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/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 000000000..a082fb340 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,15 @@ +{ + "image": "mcr.microsoft.com/devcontainers/base:ubuntu-24.04", + "features": { + "ghcr.io/devcontainers/features/python:1": { + "installTools": true, + "version": "3.11" + }, + "ghcr.io/devcontainers-contrib/features/gdbgui:2": { + "version": "latest" + }, + "ghcr.io/rocker-org/devcontainer-features/apt-packages:1": { + "packages": "automake,autoconf,gdb" + } + } +} diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 80490efbe..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.11.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 3cb531daa..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.8', '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 c671b9bd9..7125996bd 100644 --- a/.github/workflows/tools.yml +++ b/.github/workflows/tools.yml @@ -50,7 +50,7 @@ jobs: - run: make osx-dmg - uses: actions/upload-artifact@v4 with: - name: artifact-macos + name: artifact-osx path: "*.dmg" windows: diff --git a/.gitignore b/.gitignore index 3ac8183fe..ebc21507c 100644 --- a/.gitignore +++ b/.gitignore @@ -36,3 +36,4 @@ missing gambit .python-version dist +.venv diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3a601efac..501002daa 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v3.2.0 + rev: v4.6.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer @@ -11,11 +11,11 @@ repos: hooks: - id: ruff - repo: https://github.com/pycqa/flake8 - rev: 7.0.0 + rev: 7.1.0 hooks: - id: flake8 - repo: https://github.com/MarcoGorelli/cython-lint - rev: v0.16.0 + rev: v0.16.2 hooks: - id: cython-lint - id: double-quote-cython-strings diff --git a/ChangeLog b/ChangeLog index 932528071..70ba908af 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,74 @@ # Changelog +## [16.4.0] - unreleased + +### 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) +- Implement `Nodes` collection as a member of `GameRep`, including a C++ iterator that + returns nodes in depth-first traversal order (#530) + + +## [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 + +### General +- Dropped support for Python 3.8. + +### Added +- Implemented maximum-likelihood estimation for agent logit QRE, to parallel existing support + for strategic logit QRE. Strategic logit QRE function names have been modified to provide + parallel naming. Estimation using the correspondence now supports an option to stop at the + first interior local maximizer found (if one exists). +- Maximum-likelihood estimation for logit QRE using empirical payoffs has an improved internal + calculation of log-likelihood, and returns the estimated profile instead of just a list of + probabilities. +- Reorganized naming conventions in pygambit for functions for computing QRE in both strategic + and agent versions, and added a corresponding section in the user guide. +- `enumpoly_solve` has been returned to being fully supported from temporarily being experimental; + now available in `pygambit`. +- `enumpoly_solve` for strategic games now uses the Porter, Nudelman, and Shoham (2004) ordering + of supports to search. +- `to_arrays` converts a `Game` to a list of `numpy` arrays containing its reduced strategic form. + (#461) +- Integrated support (in Python) for using `PHCpack` to solve systems of polynomial equations in + `enumpoly_solve` based on an implementation formerly in the `contrib` section of the + repository. (#165) +- New format-specific functions `pygambit.read_*` and `pygambit.Game.to_*` functions have been + added to (de-)serialise games. The existing `Game.read_game` and `Game.write` functions have + been deprecated and will be removed in 16.4. (#357) + +### Changed +- The built-in implementation of lrslib (dating from 2016) has been removed. Instead, access to + lrsnash is provided as an external tool via the `enummixed_solve` function, in parallel to + PHCpack for `enumpoly_solve`. + +### Fixed +- When parsing .nfg files, check that the number of outcomes or payoffs is the expected number, + and raise an exception if not. (#119) + +### Removed +- `Game.write()` no longer supports generation of the XML-format files for Game Theory + Explorer, as GTE no longer reads files in this format. + + ## [16.2.2] - unreleased ### Fixed diff --git a/Makefile.am b/Makefile.am index 674c8ce32..2a19386a9 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,6 +1,6 @@ ## ## This file is part of Gambit -## Copyright (c) 1994-2023, The Gambit Project (http://www.gambit-project.org) +## Copyright (c) 1994-2025, The Gambit Project (https://www.gambit-project.org) ## ## FILE: Makefile.am ## Top-level automake input file for Gambit @@ -20,8 +20,6 @@ ## Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. ## -SUBDIRS = contrib - ACLOCAL_AMFLAGS = -I m4 EXTRA_DIST = \ @@ -247,14 +245,11 @@ 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 \ - src/core/vector.imp \ - src/core/pvector.h \ - src/core/pvector.imp \ - src/core/dvector.h \ - src/core/dvector.imp \ src/core/recarray.h \ src/core/matrix.h \ src/core/matrix.imp \ @@ -264,9 +259,6 @@ core_SOURCES = \ src/core/integer.h \ src/core/rational.cc \ src/core/rational.h \ - src/core/vector.cc \ - src/core/pvector.cc \ - src/core/dvector.cc \ src/core/matrix.cc \ src/core/sqmatrix.cc \ src/core/function.cc \ @@ -322,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 \ @@ -348,14 +335,6 @@ linalg_SOURCES = \ src/solvers/linalg/vertenum.h \ src/solvers/linalg/vertenum.imp -lrslib_SOURCES = \ - src/solvers/lrs/lrslib.h \ - src/solvers/lrs/lrslib.c \ - src/solvers/lrs/lrsmp.h \ - src/solvers/lrs/lrsmp.c \ - src/solvers/lrs/lrsnashlib.h \ - src/solvers/lrs/lrsnashlib.c - gtracer_SOURCES = \ src/solvers/gtracer/cmatrix.h \ src/solvers/gtracer/cmatrix.cc \ @@ -405,81 +384,46 @@ gambit_convert_SOURCES = \ gambit_enummixed_SOURCES = \ ${core_SOURCES} ${game_SOURCES} \ ${linalg_SOURCES} \ - ${lrslib_SOURCES} \ src/solvers/enummixed/clique.cc \ src/solvers/enummixed/clique.h \ - src/solvers/enummixed/lrsenum.cc \ src/solvers/enummixed/enummixed.cc \ src/solvers/enummixed/enummixed.h \ + src/tools/util.h \ src/tools/enummixed/enummixed.cc +gambit_nashsupport_SOURCES = \ + src/solvers/nashsupport/efgsupport.cc \ + src/solvers/nashsupport/nfgsupport.cc \ + src/solvers/nashsupport/nashsupport.h + gambit_enumpoly_SOURCES = \ - ${core_SOURCES} ${game_SOURCES} \ - src/solvers/enumpoly/gpartltr.cc \ - src/solvers/enumpoly/gpartltr.h \ - src/solvers/enumpoly/gpartltr.imp \ - src/solvers/enumpoly/gpoly.cc \ - src/solvers/enumpoly/gpoly.h \ - src/solvers/enumpoly/gpoly.imp \ - src/solvers/enumpoly/gpolylst.cc \ - src/solvers/enumpoly/gpolylst.h \ - src/solvers/enumpoly/gpolylst.imp \ - src/solvers/enumpoly/gsolver.cc \ - src/solvers/enumpoly/gsolver.h \ - src/solvers/enumpoly/gsolver.imp \ - src/solvers/enumpoly/ideal.cc \ - src/solvers/enumpoly/ideal.h \ - src/solvers/enumpoly/ideal.imp \ - src/solvers/enumpoly/ineqsolv.cc \ - src/solvers/enumpoly/ineqsolv.h \ - src/solvers/enumpoly/ineqsolv.imp \ - src/solvers/enumpoly/interval.h \ - src/solvers/enumpoly/monomial.cc \ - src/solvers/enumpoly/monomial.h \ - src/solvers/enumpoly/monomial.imp \ + ${core_SOURCES} ${game_SOURCES} ${gambit_nashsupport_SOURCES} \ + src/solvers/enumpoly/ndarray.h \ + src/solvers/enumpoly/gameseq.cc \ + src/solvers/enumpoly/gameseq.h \ + src/solvers/enumpoly/indexproduct.h \ + src/solvers/enumpoly/rectangle.h \ src/solvers/enumpoly/poly.cc \ src/solvers/enumpoly/poly.h \ src/solvers/enumpoly/poly.imp \ - src/solvers/enumpoly/prepoly.cc \ - src/solvers/enumpoly/prepoly.h \ - src/solvers/enumpoly/quiksolv.cc \ - src/solvers/enumpoly/quiksolv.h \ - src/solvers/enumpoly/quiksolv.imp \ - src/solvers/enumpoly/rectangl.cc \ - src/solvers/enumpoly/rectangl.h \ - src/solvers/enumpoly/rectangl.imp \ + src/solvers/enumpoly/polysystem.h \ + src/solvers/enumpoly/polypartial.h \ + src/solvers/enumpoly/polypartial.imp \ + src/solvers/enumpoly/polysolver.cc \ + src/solvers/enumpoly/polysolver.h \ + src/solvers/enumpoly/polyfeasible.h \ src/solvers/enumpoly/behavextend.cc \ src/solvers/enumpoly/behavextend.h \ - src/solvers/enumpoly/gcomplex.cc \ - src/solvers/enumpoly/gcomplex.h \ - src/solvers/enumpoly/gtree.h \ - src/solvers/enumpoly/gtree.imp \ - src/solvers/enumpoly/linrcomb.cc \ - src/solvers/enumpoly/linrcomb.h \ - src/solvers/enumpoly/linrcomb.imp \ - src/solvers/enumpoly/efgensup.cc \ - src/solvers/enumpoly/efgensup.h \ - src/solvers/enumpoly/gnarray.h \ - src/solvers/enumpoly/gnarray.imp \ - src/solvers/enumpoly/sfg.cc \ - src/solvers/enumpoly/sfg.h \ - src/solvers/enumpoly/sfstrat.cc \ - src/solvers/enumpoly/sfstrat.h \ - src/solvers/enumpoly/nfgensup.cc \ - src/solvers/enumpoly/nfgensup.h \ - src/solvers/enumpoly/odometer.cc \ - src/solvers/enumpoly/odometer.h \ - src/solvers/enumpoly/nfgcpoly.cc \ - src/solvers/enumpoly/nfgcpoly.h \ - src/solvers/enumpoly/nfghs.cc \ - src/solvers/enumpoly/nfghs.h \ 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 = \ @@ -496,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 = \ @@ -504,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 = \ @@ -511,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 = \ @@ -519,25 +466,25 @@ gambit_logit_SOURCES = \ src/solvers/logit/logbehav.imp \ src/solvers/logit/path.cc \ src/solvers/logit/path.h \ - src/solvers/logit/efglogit.h \ + src/solvers/logit/logit.h \ src/solvers/logit/efglogit.cc \ - src/solvers/logit/nfglogit.h \ 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 = \ @@ -691,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/configure.ac b/configure.ac index be1eed43e..41be2c377 100644 --- a/configure.ac +++ b/configure.ac @@ -20,7 +20,7 @@ dnl along with this program; if not, write to the Free Software dnl Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. dnl -AC_INIT([gambit],[16.2.1]) +AC_INIT([gambit],[16.3.0]) AC_CONFIG_SRCDIR([src/gambit.h]) AM_INIT_AUTOMAKE([subdir-objects foreign]) dnl AC_CONFIG_MACRO_DIR([m4]) @@ -145,8 +145,5 @@ AC_SUBST(WX_CXXFLAGS) REZFLAGS=`echo $CXXFLAGS $WX_CXXFLAGS | sed 's/-mthreads//g' | sed 's/-g//g' | sed 's/-O. / /g' | sed 's/-I/--include-dir /g'` AC_SUBST(REZFLAGS) -AC_CONFIG_FILES([Makefile - contrib/Makefile - contrib/scripts/Makefile - contrib/scripts/enumpoly/Makefile]) +AC_CONFIG_FILES([Makefile]) AC_OUTPUT diff --git a/contrib/Makefile.am b/contrib/Makefile.am deleted file mode 100644 index c7a846959..000000000 --- a/contrib/Makefile.am +++ /dev/null @@ -1,23 +0,0 @@ -## -## This file is part of Gambit -## Copyright (c) 1994-2023, The Gambit Project (http://www.gambit-project.org) -## -## FILE: contrib/Makefile.am -## automake input file for contrib subdirectory -## -## 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. -## - -SUBDIRS = scripts diff --git a/contrib/games/nim.efg b/contrib/games/nim.efg index 00a941189..7f08b92a6 100644 --- a/contrib/games/nim.efg +++ b/contrib/games/nim.efg @@ -1,4 +1,4 @@ -EFG 2 R "Nim, starting with Five Stones." { "Player 1" "Player 2" } +EFG 2 R "Nim-like game. One pile of 5 stones. Players alternately take 1 or 2 stones. Player to take last stone wins." { "Player 1" "Player 2" } p "5 left" 1 1 "(1,1)" { "TAKE 1" "TAKE 2" } 0 p "4 left" 2 1 "(2,1)" { "TAKE 1" "TAKE 2" } 0 diff --git a/contrib/games/nim7.efg b/contrib/games/nim7.efg index f1252926f..e0085cb45 100644 --- a/contrib/games/nim7.efg +++ b/contrib/games/nim7.efg @@ -1,4 +1,4 @@ -EFG 2 R "Nim, starting with Seven Stones." { "Player 1" "Player 2" } +EFG 2 R "Nim-like game. One pile of 7 stones. Players alternately take 1 or 2 stones. Player to take last stone wins." { "Player 1" "Player 2" } p "7 left" 2 1 "(2,4)" { "take 2" "take 1" } 0 p "5 left" 1 1 "(1,1)" { "take_2" "take_1" } 0 diff --git a/contrib/mac/Info.plist b/contrib/mac/Info.plist index 16cfe66ad..148bbf25b 100644 --- a/contrib/mac/Info.plist +++ b/contrib/mac/Info.plist @@ -19,13 +19,13 @@ CFBundleSignature ???? CFBundleVersion - 16.2.0 + 16.3.0 CFBundleShortVersionString - 16.2.0 + 16.3.0 CFBundleGetInfoString - Gambit version 16.2.1, (c) 1994-2025 The Gambit Project + Gambit version 16.3.0, (c) 1994-2025 The Gambit Project CFBundleLongVersionString - 16.2.1, (c) 1994-2025 The Gambit Project + 16.3.0, (c) 1994-2025 The Gambit Project NSHumanReadableCopyright Copyright 1994-2025 The Gambit Project LSRequiresCarbon diff --git a/contrib/scripts/Makefile.am b/contrib/scripts/Makefile.am deleted file mode 100644 index ad24b702c..000000000 --- a/contrib/scripts/Makefile.am +++ /dev/null @@ -1,25 +0,0 @@ -## -## This file is part of Gambit -## Copyright (c) 1994-2023, The Gambit Project (http://www.gambit-project.org) -## -## FILE: contrib/scripts/Makefile.am -## automake input for sample scripts directory -## -## 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. -## - -SUBDIRS = enumpoly - -EXTRA_DIST = diff --git a/contrib/scripts/enumpoly/Makefile.am b/contrib/scripts/enumpoly/Makefile.am deleted file mode 100644 index 0441bdfb5..000000000 --- a/contrib/scripts/enumpoly/Makefile.am +++ /dev/null @@ -1,15 +0,0 @@ -# -# $Source$ -# $Date: 2005-12-12 01:22:08 -0600 (Mon, 12 Dec 2005) $ -# $Revision: 6040 $ -# -# DESCRIPTION: -# automake input for sample scripts directory -# - -EXTRA_DIST = \ - countsupport.py \ - enumpoly.py \ - phc.py \ - setup.py \ - README diff --git a/contrib/scripts/enumpoly/README b/contrib/scripts/enumpoly/README deleted file mode 100644 index 4b111ae2a..000000000 --- a/contrib/scripts/enumpoly/README +++ /dev/null @@ -1,111 +0,0 @@ -gambit-enumphc version 0.2007.03.12 -=================================== - -This is the README for gambit-enumphc, which attempts to compute all -(isolated) Nash equilibria of a strategic game by enumerating possible supports -and solving the corresponding systems of polynomial equations. - -This program is similar in principle to the gambit-enumpoly program in -the Gambit distribution. It differs in practice in two (useful) ways: - -(1) The support enumeration method is slightly different (although it -appears to produce identical output, just in a different order). -The correctness of the implementation here has been established -formally, in that it will (a) visit any support at most once, and -(b) visit any support that may harbor a Nash equilibrium. It is somewhat -more efficient than the implementation in gambit-enumpoly, although -the support enumeration process is typically a small portion of the -overall running time of this method. Also, this implementation solves -on each support as it is generated, as opposed to gambit-enumpoly, which -first generates the list of candidate supports, and then solves them. - -(2) The backend is the PHCPACK solver of Jan Verschelde. This program -has been tested with version 2.3.24 of PHCPACK. PHCPACK source, and -binaries for several systems, can be obtained either from the main -PHCPACK site at - -http://www.math.uic.edu/~jan/download.html - -or from the Gambit website - -http://econweb.tamu.edu/gambit/download.html - -The version of PHCPACK mirrored on the Gambit site has been tested with -this program. Newer versions of PHCPACK may be available from the -main site; these should also work with this program, but there is always -the possibility of incompatible changes in PHCPACK's output. - -The PHCPACK "black-box" solver for computing solutions to a system of -polynomial equations, which is called by this program, is much more -reliable than the polynomial system solver used in gambit-enumpoly. - -The program is written in Python, and has been tested with Python 2.4. -It requires that the Gambit Python extension (available from the -Gambit website) has been installed. The program is invoked as - -python enumphc.py [options] < game.nfg - -Consult the gambit-enumpoly documentation for general information about -the method; this program can be thought of as a "drop-in" replacement -for that program (at least for strategic games). What follows -documents some differences and usage notes for this program. Note that -this program is still young, and may require some care and feeding in -terms of the tolerances below. - -Additional options available: --p: Specify a prefix for the files used to communicate with PHCPACK. - In calling the PHCPACK executable, this program creates a file - with the polynomial system that is passed to PHCPACK, and PHCPACK - communicates the solutions back via another file. If you run - multiple instances of this program at once, you will want to specify - different prefixes for these files to use. - --t: Payoff tolerance. There are typically many solutions to the system - of equations for a support that are not Nash equilibria. Some of - these solutions fail to be Nash because a strategy not in the support - has a higher payoff than the strategies in the support. This - parameter adjusts the tolerance for strategies with superior payoffs - outside the support; it defaults to 1.0e-6, meaning that a profile - is reported to be Nash even if there exists strategies with superior - payoffs outside the support, so long as the regret is no more than - 1.0e-6. Note that if your payoff scales are large, you will almost - certainly want to modify this (1.0e-6 seems about appropriate for - games whose payoffs are on [0, 10] or so). - --n: Negative probability tolerance. By default, any solution with a - negative probability, no matter how small, is not regarded as Nash. - Giving a number greater than zero for this parameter allows solutions - with negative probabilities no greater in magnitude than the parameter - to be considered Nash. - -Both the -t and -n parameters attempt to address situations in which the -game being solved is "almost non-generic", in the sense that there is a -nearby game that is degenerate. - -By default the program only reports equilibria found. Using the -v -command-line switch adds three additional pieces of information to the -output: -(1) candidate supports: as each support is visited, its contents are - output with the tag "candidate" -(2) singular supports: If for any reason PHCPACK returns with an error - on a system, that support is flagged as "singular". This might - indicate a bug in PHCPACK; more often, it may indicate that a game - is not generic, in that this support (or a closely related one) might - harbor a positive-dimensional continuum of equilibria. The - program currently does not attempt to further characterize equilibria - on such supports (but hopes to do so in the future). -(3) non-equilibrium profiles: All solutions to the system of equations - which are not Nash equilibria are reported, prefixed by the tag - "noneqm". In addition, to these lines is appended the Lyapunov value - of the profile. This is a nonnegative value which is zero exactly - at Nash equilibria. Small values of the Lyapunov value might indicate - that the profile is in fact an approximate Nash equilibrium, i.e., - there is in fact an equilibrium nearby, but the profile fails the - equilibrium test for numerical reasons. This value can thus be used - as a diagnostic for setting the -t and -n parameters (above) appropriately. - -This program is described in "Towards a Black-Box Solver for Finte -Games: Finding All Nash Equilibria with Gambit and PHCpack," which is -available at - -http://econweb.tamu.edu/turocy/papers/enumpoly.html diff --git a/contrib/scripts/enumpoly/countsupport.py b/contrib/scripts/enumpoly/countsupport.py deleted file mode 100644 index c172f3f03..000000000 --- a/contrib/scripts/enumpoly/countsupport.py +++ /dev/null @@ -1,40 +0,0 @@ -import random -import enumphc -import randomnfg - -if __name__ == '__main__': - import sys, os - - #os.system("rm game-*.nfg") - - argv = sys.argv - solve = True - - if argv[1] == '-e': - solve = False - argv = [ argv[0] ] + argv[2:] - - game = randomnfg.CreateNfg([int(x) for x in argv[2:]]) - strategies = [ strategy - for player in game.Players() - for strategy in player.Strategies() ] - - print "ownuser,ownsystem,childuser,childsystem,supports,singular,nash,nonnash" - for iter in xrange(int(argv[1])): - randomnfg.RandomizeGame(game, lambda: random.normalvariate(0, 1)) - #file("game-%04d.nfg" % iter, "w").write(game.AsNfgFile()) - logger = enumphc.CountLogger() - startTime = os.times() - enumphc.EnumViaPHC(game, "blek", logger, solve=solve) - endTime = os.times() - - print ("%f,%f,%f,%f,%d,%d,%d,%d" % - (endTime[0] - startTime[0], - endTime[1] - startTime[1], - endTime[2] - startTime[2], - endTime[3] - startTime[3], - logger.candidates, - logger.singulars, - logger.nash, - logger.nonnash)) - sys.stdout.flush() diff --git a/contrib/scripts/enumpoly/enumpoly.py b/contrib/scripts/enumpoly/enumpoly.py deleted file mode 100644 index b8f7cbe56..000000000 --- a/contrib/scripts/enumpoly/enumpoly.py +++ /dev/null @@ -1,501 +0,0 @@ -# -# Compute all equilibria of a strategic game using support enumeration. -# -# This program enumerates all "admissible" supports in a strategic game. -# The main function, Enumerate(), does the enumeration, and hands off -# each nontrivial admissible support to the Solve() method of the -# solver object provided it. -# -# This program currently offers three solver objects: -# (1) A "null" solver object (so, for instance, one can just count supports) -# (2) A "PHCSolve" solver object, which solves the implied system using -# PHCpack (external binary required) -# (3) A "Bertini" solver object, which solves the implied system using -# Bertini (external binary required) -# - -import gambit -import phc -import time, os - - -############################################################################# -# Generate polynomial equations for a support -############################################################################# - -# Use this table to assign letters to player strategy variables -# We skip 'e' and 'i', because PHC doesn't allow these in variable names. -playerletters = [ 'a', 'b', 'c', 'd', 'f', 'g', 'h', 'k', 'l', 'm', - 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', - 'x', 'y', 'z' ] - -def Equations(support, player): - payoffs = [ [] for strategy in support.Strategies(player) ] - - players = [ (pl, oplayer) - for (pl, oplayer) in enumerate(support.GetGame().Players()) - if player != oplayer ] - strategies = [ (st, strategy) - for (st, strategy) in enumerate(support.Strategies(player)) ] - for profile in gambit.StrategyIterator(support, - support.GetStrategy(player.GetNumber(), - 1)): - contingency = "*".join([playerletters[pl] + - "%d" % - (profile.GetStrategy(oplayer).GetNumber()-1) - for (pl, oplayer) in players]) - for (st, strategy) in strategies: - payoffs[st].append("(%s*%s)" % - (str(float(profile.GetStrategyValue(strategy))), - contingency)) - - equations = [ "(%s)-(%s);\n" % ("+".join(payoffs[0]), "+".join(s2)) - for s2 in payoffs[1:] ] - - # Substitute in sum-to-one constraints - for (pl, oplayer) in enumerate(support.GetGame().Players()): - if player == oplayer: continue - playerchar = playerletters[pl] - if support.NumStrategies(pl+1) == 1: - for (i, equation) in enumerate(equations): - equations[i] = equation.replace("*%s%d" % (playerchar, - support.GetStrategy(pl+1, 1).GetNumber()-1), - "") - else: - subvar = "1-" + "-".join( playerchar+"%d" % (strategy.GetNumber() - 1) - for (st, strategy) in enumerate(support.Strategies(oplayer)) - if st != 0 ) - for (i, equation) in enumerate(equations): - equations[i] = equation.replace("%s%d" % (playerchar, - support.GetStrategy(pl+1, 1).GetNumber()-1), - "("+subvar+")") - - return equations - - -############################################################################# -# Utility routines -############################################################################# - -def IsNash(profile, vars): - for i in xrange(1, profile.MixedProfileLength()+1): - if profile[i] < -negTolerance: return False - - for (pl, player) in enumerate(profile.GetGame().Players()): - playerchar = playerletters[pl] - payoff = profile.GetPayoff(player) - total = 0.0 - for (st, strategy) in enumerate(player.Strategies()): - if "%s%d" % (playerchar, st) not in vars.keys(): - if profile.GetStrategyValue(strategy) - payoff > payoffTolerance: - return False - else: - total += vars["%s%d" % (playerchar, st)].real - - return True - -def CheckEquilibrium(game, support, entry, logger): - profile = game.NewMixedStrategyDouble() - - index = 1 - - for (pl, player) in enumerate(game.Players()): - playerchar = playerletters[pl] - total = 0.0 - for (st, strategy) in enumerate(player.Strategies()): - try: - profile[index] = entry["vars"][playerchar + str(st)].real - except KeyError: - profile[index] = 0.0 - total += profile[index] - index += 1 - - profile[support.GetStrategy(pl+1, 1).GetId()] = 1.0 - total - entry["vars"][playerchar + str(support.GetStrategy(pl+1,1).GetNumber()-1)] = complex(1.0 - total) - - x = ",".join([ "%f" % profile[i] - for i in xrange(1, game.MixedProfileLength() + 1)]) - if IsNash(profile, entry["vars"]): - logger.OnNashSolution(profile) - else: - logger.OnNonNashSolution(profile) - - -def IsPureSupport(support): - return reduce(lambda x, y: x and y, - [ support.NumStrategies(pl+1) == 1 - for (pl, player) in enumerate(support.Players()) ]) - -def ProfileFromPureSupport(game, support): - profile = game.NewMixedStrategyDouble() - - for index in xrange(1, game.MixedProfileLength() + 1): - profile[index] = 0.0 - - for (pl, player) in enumerate(support.Players()): - profile[support.GetStrategy(pl+1, 1).GetId()] = 1.0 - - return profile - - -############################################################################# -# Solver classes -############################################################################# - -class SupportSolver: - def __init__(self, logger): - self.logger = logger - - def GetLogger(self): return self.logger - -class NullSolver(SupportSolver): - def __init__(self, logger): - SupportSolver.__init__(self, logger) - - def Solve(support): - pass - -############################################################################# -# Solver for solving support via PHCpack -############################################################################# - -class PHCSolver(SupportSolver): - def __init__(self, prefix, logger): - SupportSolver.__init__(self, logger) - self.prefix = prefix - - def GetFilePrefix(self): return self.prefix - - def Solve(self, support): - game = support.GetGame() - - eqns = reduce(lambda x, y: x + y, - [ Equations(support, player) - for player in game.Players() ]) - - phcinput = ("%d\n" % len(eqns)) + "".join(eqns) - #print phcinput - - try: - phcoutput = phc.RunPHC("./phc", self.prefix, phcinput) - #print "%d solutions found; checking for equilibria now" % len(phcoutput) - for entry in phcoutput: - CheckEquilibrium(game, support, entry, self.logger) - - #print "Equilibrium checking complete" - except ValueError: - self.logger.OnSingularSupport(support) - except: - self.logger.OnSingularSupport(support) - - -############################################################################# -# Solver for solving support via Bertini -############################################################################# - -class BertiniSolver(SupportSolver): - def __init__(self, logger, bertiniPath="./bertini"): - SupportSolver.__init__(self, logger) - self.bertiniPath = bertiniPath - - def GetBertiniPath(self): return self.bertiniPath - def SetBertiniPath(self, path): self.bertiniPath = path - - def Solve(self, support): - game = support.GetGame() - - eqns = reduce(lambda x, y: x + y, - [ Equations(support, player) - for player in game.Players() ]) - - variables = [ ] - for (pl, player) in enumerate(game.Players()): - for st in xrange(support.NumStrategies(pl+1) - 1): - variables.append("%c%d" % (playerletters[pl], st+1)) - - bertiniInput = "CONFIG\n\nEND;\n\nINPUT\n\n" - bertiniInput += "variable_group %s;\n" % ", ".join(variables) - bertiniInput += "function %s;\n\n" % ", ".join([ "e%d" % i - for i in xrange(len(eqns)) ]) - - for (eq, eqn) in enumerate(eqns): - bertiniInput += "e%d = %s\n" % (eq, eqn) - - bertiniInput += "\nEND;\n" - - #print bertiniInput - - infile = file("input", "w") - infile.write(bertiniInput) - infile.close() - - if os.system("%s > /dev/null" % self.bertiniPath) == 0: - try: - solutions = [ ] - outfile = file("real_finite_solutions") - lines = iter(outfile) - - lines.next() - lines.next() - while lines.next().strip() != "-1": - solution = { } - solution["vars"] = { } - for var in variables: - value = lines.next().split() - solution["vars"][var] = complex(float(value[0]), - float(value[1])) - #print solution - solutions.append(solution) - outfile.close() - os.remove("real_finite_solutions") - for entry in solutions: - CheckEquilibrium(game, support, entry, self.logger) - - except IOError: - #print "couldn't open real_finite_solutions" - self.logger.OnSingularSupport(support) - - - -############################################################################# -# Logger classes for storing and reporting output -############################################################################# - -class NullLogger: - def OnCandidateSupport(self, support): pass - def OnSingularSupport(self, support): pass - def OnNashSolution(self, profile): pass - def OnNonNashSolution(self, profile): pass - -class StandardLogger(NullLogger): - def OnNashSolution(self, profile): - print "NE," + ",".join(["%f" % max(profile[i], 0.0) - for i in xrange(1, profile.MixedProfileLength() + 1)]) - -class VerboseLogger(StandardLogger): - def PrintSupport(self, support, label): - strings = [ reduce(lambda x, y: x + y, - [ str(int(support.Contains(strategy))) - for strategy in player.Strategies() ]) - for player in support.Players() ] - print label + "," + ",".join(strings) - - def OnCandidateSupport(self, support): - self.PrintSupport(support, "candidate") - def OnSingularSupport(self, support): - self.PrintSupport(support, "singular") - - def OnNonNashSolution(self, profile): - print ("noneqm," + - ",".join([str(profile[i]) - for i in xrange(1, profile.MixedProfileLength() + 1)]) + - "," + str(profile.GetLiapValue())) - - -class CountLogger: - def __init__(self): - self.candidates = 0 - self.singulars = 0 - self.nash = 0 - self.nonnash = 0 - - def OnCandidateSupport(self, support): self.candidates += 1 - def OnSingularSupport(self, support): self.singulars += 1 - def OnNashSolution(self, profile): self.nash += 1 - def OnNonNashSolution(self, profile): self.nonnash += 1 - - -############################################################################# -# The main program: enumerate supports and pass them to a solver class -############################################################################# - -# -# Compute the admissible supports such that all strategies in 'setin' -# are in the support, all the strategies in 'setout' are not in the -# support. -# -# setin: Set of strategies assumed "in" the support -# setout: Set of strategies assumed "outside" the support -# setfree: Set of strategies not yet allocated -# These form a partition of the set of all strategies -# -def AdmissibleSubsupports(game, setin, setout, setfree, skipdom = False): - #print time.time(), len(setin), len(setout) - support = gambit.StrategySupport(game) - for strategy in setout: - if not support.RemoveStrategy(strategy): - # If the removal fails, it means we have no strategies - # for this player; we can stop - raise StopIteration - if setfree == []: - # We have assigned all strategies. Now check for dominance. - # Note that we check these "by hand" because when eliminating - # by "external" dominance, the last strategy for a player - # won't be eliminated, even if it should, because RemoveStrategy() - # will not allow the last strategy for a player to be removed. - # Need to consider whether the API should be modified for this. - for player in game.Players(): - for strategy in support.Strategies(player): - if support.IsDominated(strategy, True, True): - raise StopIteration - yield support - else: - #print "Starting iterated dominance" - if not skipdom: - while True: - newsupport = support.Undominated(True, True) - if newsupport == support: break - support = newsupport - #print "Done with iterated dominance" - - for strategy in setin: - if not support.Contains(strategy): - # We dropped a strategy already assigned as "in": - # we can terminate this branch of the search - raise StopIteration - - subsetout = setout[:] - subsetfree = setfree[:] - - for strategy in setfree: - if not newsupport.Contains(strategy): - subsetout.append(strategy) - subsetfree.remove(strategy) - - else: - subsetout = setout[:] - subsetfree = setfree[:] - - # Switching the order of the following two calls (roughly) - # switches whether supports are tried largest first, or - # smallest first - # Currently: smallest first - - # When we add a strategy to 'setin', we can skip the dominance - # check at the next iteration, because it will return the same - # result as here - - for subsupport in AdmissibleSubsupports(game, - setin, - subsetout + [subsetfree[0]], - subsetfree[1:]): - yield subsupport - - for subsupport in AdmissibleSubsupports(game, - setin + [subsetfree[0]], - subsetout, subsetfree[1:]): - yield subsupport - - - raise StopIteration - -def Enumerate(game, solver): - game.BuildComputedValues() - - for support in AdmissibleSubsupports(game, [], [], - [ strategy - for player in game.Players() - for strategy in player.Strategies() ]): - solver.GetLogger().OnCandidateSupport(support) - - if IsPureSupport(support): - # By definition, if this is a pure-strategy support, it - # must be an equilibrium (because of the dominance - # elimination process) - solver.GetLogger().OnNashSolution(ProfileFromPureSupport(game, support)) - else: - solver.Solve(support) - -######################################################################## -# Instrumentation for standalone use -######################################################################## - -def PrintBanner(f): - f.write("Compute Nash equilibria by solving polynomial systems\n") - f.write("(solved using Jan Verschelde's PHCPACK solver)\n") - f.write("Gambit version 0.2007.03.12, Copyright (C) 2007, The Gambit Project\n") - f.write("This is free software, distributed under the GNU GPL\n\n") - -def PrintHelp(progname): - PrintBanner(sys.stderr) - sys.stderr.write("Usage: %s [OPTIONS]\n" % progname) - sys.stderr.write("Accepts game on standard input.\n") - sys.stderr.write("With no options, reports all Nash equilibria found.\n\n") - sys.stderr.write("Options:\n") - sys.stderr.write(" -h print this help message\n") - sys.stderr.write(" -m backend to use (null, phc, bertini)\n") - sys.stderr.write(" -n tolerance for negative probabilities (default 0)\n") - sys.stderr.write(" -p prefix to use for filename in calling PHC\n") - sys.stderr.write(" -q quiet mode (suppresses banner)\n") - sys.stderr.write(" -t tolerance for non-best-response payoff (default 1.0e-6)\n") - sys.stderr.write(" -v verbose mode (shows supports investigated)\n") - sys.stderr.write(" (default is only to show equilibria)\n") - sys.exit(1) - -payoffTolerance = 1.0e-6 -negTolerance = 0.0 - -if __name__ == '__main__': - import getopt, sys - - verbose = False - quiet = False - prefix = "blek" - backend = "phc" - - try: - opts, args = getopt.getopt(sys.argv[1:], "p:n:t:hqvd:m:") - except getopt.GetoptError: - PrintHelp(sys.argv[0]) - - for (o, a) in opts: - if o == "-h": - PrintHelp(sys.argv[0]) - elif o == "-d": - pass - elif o == "-v": - verbose = True - elif o == "-q": - quiet = True - elif o == "-p": - prefix = a - elif o == "-m": - if a in [ "null", "phc", "bertini" ]: - backend = a - else: - sys.stderr.write("%s: unknown backend `%s' passed to `-m'\n" % - (sys.argv[0], a)) - sys.exit(1) - elif o == "-n": - try: - negTolerance = float(a) - except ValueError: - sys.stderr.write("%s: `-n' option expects a floating-point number\n" % sys.argv[0]) - sys.exit(1) - elif o == "-t": - try: - payoffTolerance = float(a) - except ValueError: - sys.stderr.write("%s: `-t' option expects a floating-point number\n" % sys.argv[0]) - else: - sys.stderr.write("%s: Unknown option `-%s'.\n" % - (sys.argv[0], o)) - sys.exit(1) - - if not quiet: - PrintBanner(sys.stderr) - - game = gambit.ReadGame(sys.stdin.read()) - game.BuildComputedValues() - if verbose: - logger = VerboseLogger() - else: - logger = StandardLogger() - - if backend == "bertini": - Enumerate(game, solver=BertiniSolver(logger, - bertiniPath="./bertini")) - elif backend == "phc": - Enumerate(game, solver=PHCSolver(prefix, logger)) - else: - Enumerate(game, solver=NullSolver(logger)) diff --git a/contrib/scripts/enumpoly/phc.py b/contrib/scripts/enumpoly/phc.py deleted file mode 100644 index 6cb3ce458..000000000 --- a/contrib/scripts/enumpoly/phc.py +++ /dev/null @@ -1,143 +0,0 @@ -########################################################################### -# A Python interface to PHCPACK -########################################################################### - -# This takes the entire output of a PHCPACK run, and returns a list of -# solutions. -# Each solution is a Python dictionary with the following entries, -# each containing one of the (corresponding) data from a solution: -# * "startresidual" -# * "iterations" -# * "result" -# * "t" -# * "m" -# * "err" -# * "rco" -# * "res" -# -# There are two other dictionary entries of interest: -# * "type": the text string PHCPACK emits describing the output -# (e.g., "no solution", "real regular", etc.) -# * "vars": a dictionary whose keys are the variable names, -# and whose values are the solution values, represented -# using the Python `complex` type. -def ProcessPHCOutput(output): - # This initial phase identifies the part of the output containing - # the solutions. It is complicated slightly because (as of Jan 2007) - # PHCPACK's blackbox solver uses special-case code for linear and - # sparse systems (this per email from Jan Verschelde). When these - # methods are in effect, the output format is slightly different. - - startsol = output.find("THE SOLUTIONS :\n\n") - - if startsol == -1: - startsol = output.find("THE SOLUTIONS :\n") - - solns = output[startsol:] - - firstequals = solns.find("solution") - firstcut = solns[firstequals:] - - secondequals = firstcut.find("=====") - if secondequals >= 0: - secondcut = firstcut[:secondequals] - else: - secondequals = firstcut.find("TIMING") - secondcut = firstcut[:secondequals] - - solutions = [ ] - - for line in secondcut.split("\n"): - tokens = [ x.strip(" ") - for x in line.split(" ") - if x != "" and not x.isspace() ] - - if tokens == []: continue - - if tokens[0] == "solution": - if len(tokens) == 3: - # This is a solution that didn't involve iteration - solutions.append( { "vars": { } } ) - else: - solutions.append({ "startresidual": float(tokens[6]), - "iterations": int(tokens[9]), - "result": tokens[10], - "vars": { } }) - elif tokens[0] == "t": - solutions[-1]["t"] = complex(float(tokens[2]), - float(tokens[3])) - elif tokens[0] == "m": - solutions[-1]["m"] = int(tokens[2]) - elif tokens[0] == "the": - pass - elif tokens[0] == "==": - solutions[-1]["err"] = float(tokens[3]) - solutions[-1]["rco"] = float(tokens[7]) - solutions[-1]["res"] = float(tokens[11]) - try: - solutions[-1]["type"] = " ".join([tokens[13], tokens[14]]) - except IndexError: - # Some solutions don't have type information - pass - else: - # This is a solution line - solutions[-1]["vars"][tokens[0]] = complex(float(tokens[2]), - float(tokens[3])) - - return solutions - - -import os - -# This sets up and processes a PHC run. -# 'phcpath' is the full path to PHC on the system; -# 'equations' is a text string containing the system in PHC's input format -# Returns a list of the solutions -# (see the comments on ProcessPHCOutput() above for the description of -# each of these solution entries) -def RunPHC(phcpath, filename, equations): - infilename = filename - outfilename = filename + ".phc" - - infile = file(infilename, "w") - infile.write(equations) - infile.write("\n\n") - infile.close() - - if os.system(" ".join([phcpath, "-b", infilename, outfilename])) == 0: - outfile = file(outfilename) - output = outfile.read() - outfile.close() - else: - # For convenience, print the equation sets that cause problems - print equations - os.remove(infilename) - os.remove(outfilename) - raise ValueError, "PHC run failed" - - os.remove(outfilename) - os.remove(infilename) - - return ProcessPHCOutput(output) - -if __name__ == '__main__': - # 2x2.nfg, full support - output = RunPHC("./phc", "foo", - "4\n" - "2*b1 - b2;\n" - "b1 + b2 - 1;\n" - "a2 - a1;\n" - "a1 + a2 - 1;\n") - print output - - # 2x2x2.nfg, full support - output = RunPHC("./phc", "foo", - "6\n" - "9*b1*c1+3*b2*c2-(3*b1*c2+9*b2*c1);\n" - "8*a1*c1+4*a2*c2-(4*a1*c2+8*a2*c1);\n" - "12*a1*b1+2*a2*b2-(6*a1*b2+6*a2*b1);\n" - "a1+a2-1;\n" - "b1+b2-1;\n" - "c1+c2-1;\n" - ) - print output diff --git a/contrib/scripts/enumpoly/setup.py b/contrib/scripts/enumpoly/setup.py deleted file mode 100644 index abb8af826..000000000 --- a/contrib/scripts/enumpoly/setup.py +++ /dev/null @@ -1,9 +0,0 @@ -# This is a distutils/py2exe script to build the Windows binary version -# of gambit-enumphc - -from distutils.core import setup -import py2exe - -setup(console=["enumphc.py"], - data_files=[(".", - [ "phc.exe", "README" ])]) diff --git a/doc/conf.py b/doc/conf.py index e20f458ab..e8098594f 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -49,9 +49,9 @@ # built documents. # # The short X.Y version. -version = "16.2" +version = "16.3" # The full version, including alpha/beta/rc tags. -release = "16.2.1" +release = "16.3.0" # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/doc/formats.bagg.rst b/doc/formats.bagg.rst index 356314548..ef2a20955 100644 --- a/doc/formats.bagg.rst +++ b/doc/formats.bagg.rst @@ -38,4 +38,4 @@ separated by whitespaces. Lines with starting '#' are treated as comments and ar #. utility function for each action node: same as in `the AGG format`_. -.. _the AGG format: file-formats-agg_ +.. _the AGG format: _file-formats-agg diff --git a/doc/index.rst b/doc/index.rst index 691e67368..2dd8f21dd 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -33,7 +33,7 @@ made. If you are citing Gambit in a paper, we suggest a citation of the form: Savani, Rahul and Turocy, Theodore L. (2025) - Gambit: The package for computation in game theory, Version 16.2.1. + Gambit: The package for computation in game theory, Version 16.3.0. https://www.gambit-project.org. Replace the version number and year as appropriate if you use a diff --git a/doc/intro.rst b/doc/intro.rst index ad26eefc5..68c8d7caa 100644 --- a/doc/intro.rst +++ b/doc/intro.rst @@ -183,7 +183,7 @@ Downloading Gambit ================== Gambit source code and built binaries can be downloaded from the project -`GitHub repository releases section`. +`GitHub repository releases section `_. Older versions of Gambit can be downloaded from `http://sourceforge.net/projects/gambit/files diff --git a/doc/pygambit.api.rst b/doc/pygambit.api.rst index e1a8a1710..0b9de26e9 100644 --- a/doc/pygambit.api.rst +++ b/doc/pygambit.api.rst @@ -28,13 +28,20 @@ Creating, reading, and writing games .. autosummary:: :toctree: api/ + read_gbt + read_efg + read_nfg + read_agg + Game.new_tree Game.new_table Game.from_arrays + Game.to_arrays Game.from_dict - Game.read_game - Game.parse_game - Game.write + Game.to_efg + Game.to_nfg + Game.to_html + Game.to_latex Transforming game trees @@ -138,6 +145,7 @@ Information about the game Node.infoset Node.player Node.is_successor_of + Node.plays .. autosummary:: @@ -150,6 +158,7 @@ Information about the game Infoset.actions Infoset.members Infoset.precedes + Infoset.plays .. autosummary:: @@ -159,6 +168,7 @@ Information about the game Action.infoset Action.precedes Action.prob + Action.plays .. autosummary:: @@ -168,6 +178,7 @@ Information about the game Strategy.game Strategy.player Strategy.number + Strategy.action Player behavior @@ -180,7 +191,7 @@ Player behavior Game.random_strategy_profile Game.mixed_behavior_profile Game.random_behavior_profile - Game.support_profile + Game.strategy_support_profile Representation of strategic behavior @@ -205,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 @@ -232,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 @@ -279,6 +292,7 @@ Computation of Nash equilibria NashComputationResult enumpure_solve enummixed_solve + enumpoly_solve lp_solve lcp_solve liap_solve @@ -296,6 +310,8 @@ Computation of quantal response equilibria .. autosummary:: :toctree: api/ - fit_empirical - fit_fixedpoint + logit_solve_branch + logit_solve_lambda + logit_estimate LogitQREMixedStrategyFitResult + LogitQREMixedBehaviorFitResult diff --git a/doc/pygambit.user.rst b/doc/pygambit.user.rst index 23574f6bc..26a46cc98 100644 --- a/doc/pygambit.user.rst +++ b/doc/pygambit.user.rst @@ -183,6 +183,29 @@ the top level index is the choice of the first player, the second level index of and so on. Therefore, to create a two-player symmetric game, as in this example, the payoff matrix for the second player is transposed before passing to :py:meth:`.Game.from_arrays`. +There is a reverse function :py:meth:`.Game.to_arrays` that produces +the players' payoff tables given a strategic game. The output is the list of ``numpy`` arrays, +where the number of produced arrays is equal to the number of players. + +.. ipython:: python + + m, m_transposed = g.to_arrays() + m + +The optional parameter `dtype`` controls the data type of the payoffs in the generated arrays. + +.. ipython:: python + + m, m_transposed = g.to_arrays(dtype=float) + m + +The function supports any type which can convert from Python's `fractions.Fraction` type. +For example, to convert the payoffs to their string representations via `str`: + +.. ipython:: python + + m, m_transposed = g.to_arrays(dtype=str) + m .. _pygambit.user.numbers: @@ -292,7 +315,7 @@ the values are recorded as intended. Reading a game from a file ~~~~~~~~~~~~~~~~~~~~~~~~~~ -Games stored in existing Gambit savefiles can be loaded using :meth:`.Game.read_game`: +Games stored in existing Gambit savefiles can be loaded using :meth:`.read_efg` or :meth:`.read_nfg`: .. ipython:: python :suppress: @@ -302,7 +325,7 @@ Games stored in existing Gambit savefiles can be loaded using :meth:`.Game.read_ .. ipython:: python - g = gbt.Game.read_game("e02.nfg") + g = gbt.read_nfg("e02.nfg") g .. ipython:: python @@ -340,7 +363,7 @@ the extensive representation. Assuming that ``g`` refers to the game .. ipython:: python :suppress: - g = gbt.Game.read_game("poker.efg") + g = gbt.read_efg("poker.efg") .. ipython:: python @@ -539,7 +562,7 @@ As an example, consider solving the standard one-card poker game using .. ipython:: python - g = gbt.Game.read_game("poker.efg") + g = gbt.read_efg("poker.efg") g.max_payoff, g.min_payoff :py:func:`.logit_solve` is a globally-convergent method, in that it computes a @@ -623,7 +646,7 @@ strategies for each player), :py:func:`.liap_solve` finds one of the totally-mix .. ipython:: python - g = gbt.Game.read_game("2x2x2.nfg") + g = gbt.read_nfg("2x2x2.nfg") gbt.nash.liap_solve(g.mixed_strategy_profile()) Which equilibrium is found depends on the starting point. With a different starting point, @@ -686,14 +709,13 @@ from different starting points. gbt.nash.simpdiv_solve(g.random_strategy_profile(denom=10, gen=gen)) -Estimating quantal response equilibria -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Alongside computing quantal response equilibria, Gambit can also perform maximum likelihood -estimation, computing the QRE which best fits an empirical distribution of play. +Quantal response equilibrium +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -As an example we consider an asymmetric matching pennies game studied in [Och95]_, -analysed in [McKPal95]_ using QRE. +Gambit implements the idea of [McKPal95]_ and [McKPal98]_ to compute Nash equilibria +via path-following a branch of the logit quantal response equilibrium (LQRE) correspondence +using the function :py:func:`.logit_solve`. As an example, we will consider an +asymmetric matching pennies game from [Och95]_ as analyzed in [McKPal95]_. .. ipython:: python @@ -702,13 +724,52 @@ analysed in [McKPal95]_ using QRE. [[0, 1.1141], [1.1141, 0]], title="Ochs (1995) asymmetric matching pennies as transformed in McKelvey-Palfrey (1995)" ) - data = g.mixed_strategy_profile([[128*0.527, 128*(1-0.527)], [128*0.366, 128*(1-0.366)]]) + gbt.nash.logit_solve(g) + + +:py:func:`.logit_solve` returns only the limiting (approximate) Nash equilibrium found. +Profiles along the QRE correspondence are frequently of interest in their own right. +Gambit offers several functions for more detailed examination of branches of the +QRE correspondence. + +The function :py:func:`.logit_solve_branch` uses the same procedure as :py:func:`.logit_solve`, +but returns a list of LQRE profiles computed along the branch instead of just the limiting +approximate Nash equilibrium. + +.. ipython:: python + + qres = gbt.qre.logit_solve_branch(g) + len(qres) + qres[0] + qres[5] -Estimation of QRE is done using :py:func:`.fit_fixedpoint`. +:py:func:`.logit_solve_branch` uses an adaptive step size heuristic to find points on +the branch. The parameters `first_step` and `max_accel` are used to adjust the initial +step size and the maximum rate at which the step size changes adaptively. The step size +used is computed as the distance traveled along the path, and, importantly, not the +distance as measured by changes in the precision parameter lambda. As a result the +lambda values for which profiles are computed cannot be controlled in advance. +In some situations, the LQRE profiles at specified values of lambda are of interest. +For this, Gambit provides :py:func:`.logit_solve_lambda`. This function provides +accurate values of strategy profiles at one or more specified values of lambda. .. ipython:: python - fit = gbt.qre.fit_fixedpoint(data) + qres = gbt.qre.logit_solve_lambda(g, lam=[1, 2, 3]) + qres[0] + qres[1] + qres[2] + + +LQRE are frequently taken to data by using maximum likelihood estimation to find the +LQRE profile that best fits an observed profile of play. This is provided by +the function :py:func:`.logit_estimate`. We replicate the analysis of a block +of the data from [Och95]_ for which [McKPal95]_ estimated an LQRE. + +.. ipython:: python + + data = g.mixed_strategy_profile([[128*0.527, 128*(1-0.527)], [128*0.366, 128*(1-0.366)]]) + fit = gbt.qre.logit_estimate(data) The returned :py:class:`.LogitQREMixedStrategyFitResult` object contains the results of the estimation. @@ -723,7 +784,49 @@ log-likelihood is correct for use in likelihoood-ratio tests. [#f1]_ print(fit.profile) print(fit.log_like) +All of the functions above also support working with the agent LQRE of [McKPal98]_. +Agent QRE are computed as the default behavior whenever the game has a extensive (tree) +representation. For :py:func:`.logit_solve`, :py:func:`.logit_solve_branch`, and +:py:func:`.logit_solve_lambda`, this can be overriden by passing `use_strategic=True`; +this will compute LQRE using the reduced strategy set of the game instead. +Likewise, :py:func:`.logit_estimate` will perform estimation using agent LQRE if the +data passed are a :py:class:`.MixedBehaviorProfile`, and will return a +:py:class:`.LogitQREMixedBehaviorFitResult` object. + .. rubric:: Footnotes .. [#f1] The log-likelihoods quoted in [McKPal95]_ are exactly a factor of 10 larger than those obtained by replicating the calculation. + + +Using external programs to compute Nash equilbria +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Because the problem of finding Nash equilibria can be expressed in various +mathematical formulations (see [McKMcL96]_), it is helpful to make use +of other software packages designed specifically for solving those problems. + +There are currently two integrations offered for using external programs to solve +for equilibria: + +- :py:func:`.enummixed_solve` supports enumeration of equilibria in + two-player games via `lrslib`. [#lrslib]_ +- :py:func:`.enumpoly_solve` supports computation of totally-mixed equilibria + on supports in strategic games via `PHCpack`. [#phcpack]_ + +For both calls, using the external program requires passing the path to the +executable (via the `lrsnash_path` and `phcpack_path` arguments, respectively). + +The user must download and compile or install these programs on their own; these are +not packaged with Gambit. The solver calls do take care of producing the required +input files, and reading the output to convert into Gambit objects for further +processing. + + +.. [#lrslib] http://cgm.cs.mcgill.ca/~avis/C/lrs.html + +.. [#phcpack] https://homepages.math.uic.edu/~jan/PHCpack/phcpack.html + +.. [McKMcL96] McKelvey, Richard D. and McLennan, Andrew M. (1996) Computation of equilibria + in finite games. In Handbook of Computational Economics, Volume 1, + pages 87-142. diff --git a/doc/samples.rst b/doc/samples.rst index 3ffd096ce..e70591203 100644 --- a/doc/samples.rst +++ b/doc/samples.rst @@ -60,10 +60,15 @@ Sample games to let you switch doors, should you? :download:`nim.efg <../contrib/games/nim.efg>` + This is a Nim-like game, which is a useful example of the value + of backward induction. + This version starts with one pile of five stones, and allows the + two players to alternately take 1 or 2 stones. + The player to take the last stone wins. The classic game of - `Nim `_, which is a useful example - of the value of backward induction. This version starts with five - stones. An interesting experimental study of this class of games is + `Nim `_ allows multiple piles, + and allows a player to remove any number of stones from a pile. + An interesting experimental study of Nim games is `McKinney, C. Nicholas and Van Huyck, John B. (2013) Eureka learning: Heuristics and response time in perfect information games. Games and Economic Behavior 79: diff --git a/doc/tools.convert.rst b/doc/tools.convert.rst index e13804b0e..b1f3623c0 100644 --- a/doc/tools.convert.rst +++ b/doc/tools.convert.rst @@ -40,7 +40,7 @@ Example invocation for HTML output:: $ gambit-convert -O html 2x2.nfg Convert games among various file formats - Gambit version 16.2.0, Copyright (C) 1994-2024, The Gambit Project + Gambit version 16.3.0, Copyright (C) 1994-2025, The Gambit Project This is free software, distributed under the GNU GPL

Two person 2 x 2 game with unique mixed equilibrium

@@ -55,7 +55,7 @@ Example invocation for LaTeX output:: $ gambit-convert -O sgame 2x2.nfg Convert games among various file formats - Gambit version 16.2.0, Copyright (C) 1994-2024, The Gambit Project + Gambit version 16.3.0, Copyright (C) 1994-2025, The Gambit Project This is free software, distributed under the GNU GPL \begin{game}{2}{2}[Player 1][Player 2] diff --git a/doc/tools.enummixed.rst b/doc/tools.enummixed.rst index 56bd0ba94..fb04021f7 100644 --- a/doc/tools.enummixed.rst +++ b/doc/tools.enummixed.rst @@ -70,9 +70,7 @@ in Figure 2 of Selten (International Journal of Game Theory, $ gambit-enummixed e02.nfg Compute Nash equilibria by enumerating extreme points - Gambit version 16.2.0, Copyright (C) 1994-2024, The Gambit Project - Enumeration code based on lrslib 4.2b, - Copyright (C) 1995-2005 by David Avis (avis@cs.mcgill.ca) + Gambit version 16.3.0, Copyright (C) 1994-2025, The Gambit Project This is free software, distributed under the GNU GPL NE,1,0,0,1,0 @@ -84,9 +82,7 @@ information using the `-c` switch:: $ gambit-enummixed -c e02.nfg Compute Nash equilibria by enumerating extreme points - Gambit version 16.2.0, Copyright (C) 1994-2024, The Gambit Project - Enumeration code based on lrslib 4.2b, - Copyright (C) 1995-2005 by David Avis (avis@cs.mcgill.ca) + Gambit version 16.3.0, Copyright (C) 1994-2025, The Gambit Project This is free software, distributed under the GNU GPL NE,1,0,0,1,0 diff --git a/doc/tools.enumpoly.rst b/doc/tools.enumpoly.rst index 03b79943f..790ae9adb 100644 --- a/doc/tools.enumpoly.rst +++ b/doc/tools.enumpoly.rst @@ -8,12 +8,21 @@ and inequalities. This program searches for all Nash equilibria in a strategic game using a support enumeration approach. This approach computes all the supports which could, in principle, be the support of a Nash -equilibrium, and then searches for a totally mixed equilibrium on that -support by solving a system of polynomial equalities and inequalities -formed by the Nash equilibrium conditions. The ordering of the -supports is done in such a way as to maximize use of previously -computed information, making it suited to the computation of all Nash -equilibria. +equilibrium. For each candidate support, it attempts to compute +totally mixed equilibria on that support by successively subdividing +the space of mixed strategy profiles or mixed behavior profiles (as appropriate). +By using the fact that the equilibrium conditions imply a collection +of equations and inequalities which can be expressed as multilinear +polynomials, the subdivision constructed is such that each cell +contains either no equilibria or exactly one equilibrium. + +For strategic games, the program searches supports in the order proposed +by Porter, Nudelman, and Shoham [PNS04]_. For two-player games, this +prioritises supports for which both players have the same number of +strategies. For games with three or more players, this prioritises +supports which have the fewest strategies in total. For many classes +of games, this will tend to lower the average time until finding one equilibrium, +as well as finding the second equilibrium (if one exists). When the verbose switch `-v` is used, the program outputs each support as it is considered. The supports are presented as a comma-separated @@ -22,13 +31,15 @@ digit 1 represents a strategy which is present in the support, and the digit 0 represents a strategy which is not present. Each candidate support is printed with the label "candidate,". -Note that the subroutine to compute a solution to the system of -polynomial equations and inequalities will fail in degenerate cases. +The approach of subdividing the space of totally mixed profiles assumes +solutions to the system of equations and inequalities are isolated +points. In the case of degeneracies in the resulting system, When the verbose switch `-v` is used, these supports are identified on -standard output with the label "singular,". It is possible that there -exist equilibria, often a connected component of equilibria, on these -singular supports. - +standard output with the label "singular,". This will occur +if there is a positive-dimensional set of equilibria which all +share the listed support. However, the converse is not true: +not all supports labeled as "singular" will necessarily be the +support of some set of equilibria. .. program:: gambit-enumpoly @@ -45,9 +56,7 @@ singular supports. By default, the program uses an enumeration method designed to visit as few supports as possible in searching for all equilibria. - With this switch, the program uses a heuristic search method based on - Porter, Nudelman, and Shoham [PNS04]_, which is designed to minimize the - time until the first equilibrium is found. This switch only has an + With this switch, This switch only has an effect when solving strategic games. .. cmdoption:: -S @@ -57,6 +66,20 @@ singular supports. strategies for extensive games. (This has no effect for strategic games, since a strategic game is its own reduced strategic game.) +.. cmdoption:: -m + + .. versionadded:: 16.3.0 + + Specify the maximum regret criterion for acceptance as an approximate Nash equilibrium + (default is 1e-4). See :ref:`pygambit-nash-maxregret` for interpretation and guidance. + +.. cmdoption:: -e EQA + + .. versionadded:: 16.3.0 + + By default, the program will search all support profiles. + This switch instructs the program to terminate when EQA equilibria have been found. + .. cmdoption:: -q Suppresses printing of the banner at program launch. @@ -68,18 +91,17 @@ singular supports. singular supports are identified with the label "singular." By default, no information about supports is printed. -Computing equilibria of the extensive game :download:`e01.efg +Computing equilibria of the strategic game :download:`e01.nfg <../contrib/games/e01.efg>`, the example in Figure 1 of Selten (International Journal of Game Theory, 1975) sometimes called "Selten's horse":: - $ gambit-enumpoly e01.efg + $ gambit-enumpoly e01.nfg Compute Nash equilibria by solving polynomial systems - Gambit version 16.2.0, Copyright (C) 1994-2024, The Gambit Project - Heuristic search implementation Copyright (C) 2006, Litao Wei + Gambit version 16.3.0, Copyright (C) 1994-2025, The Gambit Project This is free software, distributed under the GNU GPL + NE,1.000000,0.000000,1.000000,0.000000,0.000000,1.000000 + NE,0.000000,1.000000,1.000000,0.000000,1.000000,0.000000 NE,0.000000,1.000000,0.333333,0.666667,1.000000,0.000000 NE,1.000000,0.000000,1.000000,0.000000,0.250000,0.750000 - NE,1.000000,0.000000,1.000000,0.000000,0.000000,0.000000 - NE,0.000000,1.000000,0.000000,0.000000,1.000000,0.000000 diff --git a/doc/tools.enumpure.rst b/doc/tools.enumpure.rst index a62696357..799f73cb6 100644 --- a/doc/tools.enumpure.rst +++ b/doc/tools.enumpure.rst @@ -66,7 +66,7 @@ Computing the pure-strategy equilibria of extensive game :download:`e02.efg $ gambit-enumpure e02.efg Search for Nash equilibria in pure strategies - Gambit version 16.2.0, Copyright (C) 1994-2024, The Gambit Project + Gambit version 16.3.0, Copyright (C) 1994-2025, The Gambit Project This is free software, distributed under the GNU GPL NE,1,0,0,0,1,0 @@ -77,7 +77,7 @@ strategies:: $ gambit-enumpure -S e02.efg Search for Nash equilibria in pure strategies - Gambit version 16.2.0, Copyright (C) 1994-2024, The Gambit Project + Gambit version 16.3.0, Copyright (C) 1994-2025, The Gambit Project This is free software, distributed under the GNU GPL NE,1,0,0,1,0 @@ -88,7 +88,7 @@ only one information set; therefore the set of solutions is larger:: $ gambit-enumpure -A e02.efg Search for Nash equilibria in pure strategies - Gambit version 16.2.0, Copyright (C) 1994-2024, The Gambit Project + Gambit version 16.3.0, Copyright (C) 1994-2025, The Gambit Project This is free software, distributed under the GNU GPL NE,1,0,1,0,1,0 diff --git a/doc/tools.gnm.rst b/doc/tools.gnm.rst index 4e906706c..508f06450 100644 --- a/doc/tools.gnm.rst +++ b/doc/tools.gnm.rst @@ -88,7 +88,7 @@ the reduced strategic form of the example in Figure 2 of Selten $ gambit-gnm e02.nfg Compute Nash equilibria using a global Newton method Gametracer version 0.2, Copyright (C) 2002, Ben Blum and Christian Shelton - Gambit version 16.2.0, Copyright (C) 1994-2024, The Gambit Project + Gambit version 16.3.0, Copyright (C) 1994-2025, The Gambit Project This is free software, distributed under the GNU GPL NE,1,0,2.99905e-12,0.5,0.5 diff --git a/doc/tools.ipa.rst b/doc/tools.ipa.rst index 2b38c16f5..0dfa645b6 100644 --- a/doc/tools.ipa.rst +++ b/doc/tools.ipa.rst @@ -55,7 +55,7 @@ the reduced strategic form of the example in Figure 2 of Selten $ gambit-ipa e02.nfg Compute Nash equilibria using iterated polymatrix approximation Gametracer version 0.2, Copyright (C) 2002, Ben Blum and Christian Shelton - Gambit version 16.2.0, Copyright (C) 1994-2024, The Gambit Project + Gambit version 16.3.0, Copyright (C) 1994-2025, The Gambit Project This is free software, distributed under the GNU GPL NE,1.000000,0.000000,0.000000,1.000000,0.000000 diff --git a/doc/tools.lcp.rst b/doc/tools.lcp.rst index eb81d3c68..68a9bf0fc 100644 --- a/doc/tools.lcp.rst +++ b/doc/tools.lcp.rst @@ -58,6 +58,12 @@ game. which are subgame perfect. (This has no effect for strategic games, since there are no proper subgames of a strategic game.) +.. cmdoption:: -e EQA + + By default, the program will find all equilibria accessible from + the origin of the polytopes. This switch instructs the program + to terminate when EQA equilibria have been found. + .. cmdoption:: -h Prints a help message listing the available options. @@ -73,7 +79,7 @@ Computing an equilibrium of extensive game :download:`e02.efg $ gambit-lcp e02.efg Compute Nash equilibria by solving a linear complementarity program - Gambit version 16.2.0, Copyright (C) 1994-2024, The Gambit Project + Gambit version 16.3.0, Copyright (C) 1994-2025, The Gambit Project This is free software, distributed under the GNU GPL NE,1,0,1/2,1/2,1/2,1/2 diff --git a/doc/tools.liap.rst b/doc/tools.liap.rst index db7d58a1a..6d1c61973 100644 --- a/doc/tools.liap.rst +++ b/doc/tools.liap.rst @@ -85,7 +85,7 @@ Computing an equilibrium in mixed strategies of :download:`e02.efg $ gambit-liap e02.nfg Compute Nash equilibria by minimizing the Lyapunov function - Gambit version 16.2.0, Copyright (C) 1994-2024, The Gambit Project + Gambit version 16.3.0, Copyright (C) 1994-2025, The Gambit Project This is free software, distributed under the GNU GPL NE, 0.998701, 0.000229, 0.001070, 0.618833, 0.381167 diff --git a/doc/tools.logit.rst b/doc/tools.logit.rst index 859ee95f0..04dc0e36a 100644 --- a/doc/tools.logit.rst +++ b/doc/tools.logit.rst @@ -76,7 +76,13 @@ the beliefs at such information sets as being uniform across all member nodes. .. cmdoption:: -l While tracing, compute the logit equilibrium points - with parameter LAMBDA accurately. + with parameter LAMBDA accurately. This option may be specified multiple times, + in which case points are found for each successive lambda, in the order specified, + along the branch. + + .. versionchanged:: 16.3.0 + + Added support for specifying multiple lambda values. .. cmdoption:: -S @@ -102,7 +108,7 @@ in Figure 2 of Selten (International Journal of Game Theory, $ gambit-logit e02.nfg Compute a branch of the logit equilibrium correspondence - Gambit version 16.2.0, Copyright (C) 1994-2024, The Gambit Project + Gambit version 16.3.0, Copyright (C) 1994-2025, The Gambit Project This is free software, distributed under the GNU GPL 0.000000,0.333333,0.333333,0.333333,0.5,0.5 diff --git a/doc/tools.lp.rst b/doc/tools.lp.rst index a065328ce..486d50e59 100644 --- a/doc/tools.lp.rst +++ b/doc/tools.lp.rst @@ -65,7 +65,7 @@ strategies each, with a unique equilibrium in mixed strategies:: $ gambit-lp 2x2const.nfg Compute Nash equilibria by solving a linear program - Gambit version 16.2.0, Copyright (C) 1994-2024, The Gambit Project + Gambit version 16.3.0, Copyright (C) 1994-2025, The Gambit Project This is free software, distributed under the GNU GPL NE,1/3,2/3,1/3,2/3 diff --git a/doc/tools.rst b/doc/tools.rst index 1a4bca109..d08acbebc 100644 --- a/doc/tools.rst +++ b/doc/tools.rst @@ -39,6 +39,7 @@ documentation. tools.enumpure tools.enummixed + tools.enumpoly tools.lcp tools.lp tools.liap diff --git a/doc/tools.simpdiv.rst b/doc/tools.simpdiv.rst index 9b411e855..e29094444 100644 --- a/doc/tools.simpdiv.rst +++ b/doc/tools.simpdiv.rst @@ -92,7 +92,7 @@ Computing an equilibrium in mixed strategies of :download:`e02.efg $ gambit-simpdiv e02.nfg Compute Nash equilibria using simplicial subdivision - Gambit version 16.2.0, Copyright (C) 1994-2024, The Gambit Project + Gambit version 16.3.0, Copyright (C) 1994-2025, The Gambit Project This is free software, distributed under the GNU GPL NE,1,0,0,1,0 diff --git a/gambit.wxs b/gambit.wxs index 08876df43..c8db7f222 100644 --- a/gambit.wxs +++ b/gambit.wxs @@ -1,6 +1,6 @@ - + diff --git a/pyproject.toml b/pyproject.toml index 3c3ff6424..fcc5107c5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,10 +1,47 @@ [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 indent-width = 4 -target-version = "py38" +target-version = "py39" include = ["setup.py", "src/pygambit/**/*.py", "tests/**/*.py"] exclude = ["contrib/**.py"] @@ -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 5766141d0..7801dd292 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,6 @@ # # This file is part of Gambit -# Copyright (c) 1994-2023, The Gambit Project (http://www.gambit-project.org) +# Copyright (c) 1994-2025, The Gambit Project (https://www.gambit-project.org) # # FILE: src/python/setup.py # Setuptools configuration file for Gambit Python extension @@ -26,28 +26,66 @@ import Cython.Build import setuptools -# By compiling this separately as a C library, we avoid problems -# with passing C++-specific flags when building the extension -lrslib = ("lrslib", {"sources": glob.glob("src/solvers/lrs/*.c")}) +cppgambit_include_dirs = ["src"] +cppgambit_cflags = ( + ["-std=c++17"] if platform.system() == "Darwin" + else ["/std:c++17"] if platform.system() == "Windows" + else [] +) -cppgambit = ( - "cppgambit", +cppgambit_core = ( + "cppgambit_core", { - "sources": ( - glob.glob("src/core/*.cc") + - glob.glob("src/games/*.cc") + - glob.glob("src/games/agg/*.cc") + - [fn for fn in glob.glob("src/solvers/*/*.cc") if "enumpoly" not in fn] - ), - "include_dirs": ["src"], - "cflags": ( - ["-std=c++17"] if platform.system() == "Darwin" - else ["/std:c++17"] if platform.system() == "Windows" - else [] - ) + "sources": glob.glob("src/core/*.cc"), + "obj_deps": {"": glob.glob("src/core/*.h") + glob.glob("src/core/*.imp")}, + "include_dirs": cppgambit_include_dirs, + "cflags": cppgambit_cflags, } ) +cppgambit_games = ( + "cppgambit_games", + { + "sources": glob.glob("src/games/*.cc") + glob.glob("src/games/agg/*.cc"), + "obj_deps": { + "": ( + glob.glob("src/core/*.h") + glob.glob("src/core/*.imp") + + glob.glob("src/games/*.h") + glob.glob("src/games/*.imp") + + glob.glob("src/games/agg/*.h") + ) + }, + "include_dirs": cppgambit_include_dirs, + "cflags": cppgambit_cflags, + } +) + + +def solver_library_config(library_name: str, paths: list) -> tuple: + return ( + library_name, + { + "sources": [fn for solver in paths for fn in glob.glob(f"src/solvers/{solver}/*.cc")], + "obj_deps": { + "": ( + glob.glob("src/core/*.h") + glob.glob("src/games/*.h") + + glob.glob("src/games/agg/*.h") + + [fn for solver in paths for fn in glob.glob(f"src/solvers/{solver}/*.h")] + ) + }, + "include_dirs": cppgambit_include_dirs, + "cflags": cppgambit_cflags, + } + ) + + +cppgambit_bimatrix = solver_library_config("cppgambit_bimatrix", + ["linalg", "lp", "lcp", "enummixed"]) +cppgambit_liap = solver_library_config("cppgambit_liap", ["liap"]) +cppgambit_logit = solver_library_config("cppgambit_logit", ["logit"]) +cppgambit_gtracer = solver_library_config("cppgambit_gtracer", ["gtracer", "ipa", "gnm"]) +cppgambit_simpdiv = solver_library_config("cppgambit_simpdiv", ["simpdiv"]) +cppgambit_enumpoly = solver_library_config("cppgambit_enumpoly", ["nashsupport", "enumpoly"]) + libgambit = setuptools.Extension( "pygambit.gambit", @@ -61,47 +99,10 @@ ) ) - -def readme(): - with open("src/README.rst") as f: - return f.read() - - setuptools.setup( - name="pygambit", - version="16.2.1", - 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.8", - "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="http://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.8", - install_requires=[ - "lxml", # used for reading/writing GTE files - "numpy", - "scipy", - "deprecated", - ], - libraries=[cppgambit, lrslib], + libraries=[cppgambit_bimatrix, cppgambit_liap, cppgambit_logit, cppgambit_simpdiv, + cppgambit_gtracer, cppgambit_enumpoly, + cppgambit_games, cppgambit_core], package_dir={"": "src"}, packages=["pygambit"], ext_modules=Cython.Build.cythonize(libgambit, diff --git a/src/core/array.h b/src/core/array.h index e4d734fec..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,60 +20,89 @@ // 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 { -/// A basic bounds-checked array +/// \brief An extension of std::vector to have arbitrary offset index +/// +/// This is a variation on std::vector, which allows for the index of the first +/// element to be something other than zero. template class Array { protected: - int mindex, maxdex; - T *data; + int m_offset, m_length; + T *m_data; - /// Private helper function that accomplishes the insertion of an object int InsertAt(const T &t, int n) { - if (this->mindex > n || n > this->maxdex + 1) { + if (this->m_offset > n || n > this->last_index() + 1) { throw IndexException(); } - T *new_data = new T[++this->maxdex - this->mindex + 1] - this->mindex; + T *new_data = new T[++this->m_length] - this->m_offset; int i; - for (i = this->mindex; i <= n - 1; i++) { - new_data[i] = this->data[i]; + for (i = this->m_offset; i <= n - 1; i++) { + new_data[i] = this->m_data[i]; } new_data[i++] = t; - for (; i <= this->maxdex; i++) { - new_data[i] = this->data[i - 1]; + for (; i <= this->last_index(); i++) { + new_data[i] = this->m_data[i - 1]; } - if (this->data) { - delete[] (this->data + this->mindex); + if (this->m_data) { + delete[] (this->m_data + this->m_offset); } - this->data = new_data; + this->m_data = new_data; return n; } + T Remove(int n) + { + if (n < this->m_offset || n > this->last_index()) { + throw IndexException(); + } + + T ret(this->m_data[n]); + T *new_data = (--this->m_length > 0) ? new T[this->m_length] - this->m_offset : nullptr; + + int i; + for (i = this->m_offset; i < n; i++) { + new_data[i] = this->m_data[i]; + } + for (; i <= this->last_index(); i++) { + new_data[i] = this->m_data[i + 1]; + } + + delete[] (this->m_data + this->m_offset); + this->m_data = new_data; + + return ret; + } + public: class iterator { + friend class Array; + private: Array *m_array; int m_index; public: - using iterator_category = std::input_iterator_tag; - using difference_type = typename std::vector::iterator::difference_type; + using iterator_category = std::bidirectional_iterator_tag; + using difference_type = std::ptrdiff_t; using value_type = T; using pointer = value_type *; using reference = value_type &; - iterator(Array *p_array, int p_index) : m_array(p_array), m_index(p_index) {} + iterator(Array *p_array = 0, int p_index = 0) : m_array(p_array), m_index(p_index) {} reference operator*() { return (*m_array)[m_index]; } pointer operator->() { return &(*m_array)[m_index]; } iterator &operator++() @@ -81,6 +110,17 @@ template class Array { m_index++; return *this; } + iterator &operator--() + { + m_index--; + return *this; + } + iterator operator++(int) + { + auto ret = *this; + m_index++; + return ret; + } bool operator==(const iterator &it) const { return (m_array == it.m_array) && (m_index == it.m_index); @@ -89,13 +129,15 @@ template class Array { }; class const_iterator { + friend class Array; + private: const Array *m_array; int m_index; public: - using iterator_category = std::input_iterator_tag; - using difference_type = typename std::vector::iterator::difference_type; + using iterator_category = std::bidirectional_iterator_tag; + using difference_type = std::ptrdiff_t; using value_type = T; using pointer = value_type *; using reference = value_type &; @@ -108,6 +150,17 @@ template class Array { m_index++; return *this; } + const_iterator &operator--() + { + m_index--; + return *this; + } + const_iterator operator++(int) + { + auto ret = *this; + m_index++; + return ret; + } bool operator==(const const_iterator &it) const { return (m_array == it.m_array) && (m_index == it.m_index); @@ -115,75 +168,59 @@ template class Array { bool operator!=(const const_iterator &it) const { return !(*this == it); } }; - /// @name Lifecycle - //@{ - /// Constructs an array of length 'len', starting at '1' explicit Array(unsigned int len = 0) - : mindex(1), maxdex(len), data((len) ? new T[len] - 1 : nullptr) + : m_offset(1), m_length(len), m_data((len) ? new T[len] - 1 : nullptr) { } - /// Constructs an array starting at lo and ending at hi - Array(int lo, int hi) : mindex(lo), maxdex(hi) + Array(int lo, int hi) : m_offset(lo), m_length(hi - lo + 1) { - if (maxdex + 1 < mindex) { + if (m_length < 0) { throw RangeException(); } - data = (maxdex >= mindex) ? new T[maxdex - mindex + 1] - mindex : nullptr; + m_data = (m_length > 0) ? new T[m_length] - m_offset : nullptr; } - /// Copy the contents of another array Array(const Array &a) - : mindex(a.mindex), maxdex(a.maxdex), - data((maxdex >= mindex) ? new T[maxdex - mindex + 1] - mindex : nullptr) + : m_offset(a.m_offset), m_length(a.m_length), + m_data((m_length > 0) ? new T[m_length] - m_offset : nullptr) { - for (int i = mindex; i <= maxdex; i++) { - data[i] = a.data[i]; + for (int i = m_offset; i <= last_index(); i++) { + m_data[i] = a.m_data[i]; } } - /// Destruct and deallocates the array virtual ~Array() { - if (maxdex >= mindex) { - delete[] (data + mindex); + if (m_length > 0) { + delete[] (m_data + m_offset); } } - /// Copy the contents of another array Array &operator=(const Array &a) { if (this != &a) { - // We only reallocate if necessary. This should be somewhat faster - // if many objects are of the same length. Furthermore, it is - // _essential_ for the correctness of the PVector and DVector - // assignment operator, since it assumes the value of data does - // not change. - if (!data || (data && (mindex != a.mindex || maxdex != a.maxdex))) { - if (data) { - delete[] (data + mindex); + // We only reallocate if necessary. + if (!m_data || (m_data && (m_offset != a.m_offset || m_length != a.m_length))) { + if (m_data) { + delete[] (m_data + m_offset); } - mindex = a.mindex; - maxdex = a.maxdex; - data = (maxdex >= mindex) ? new T[maxdex - mindex + 1] - mindex : nullptr; + m_offset = a.m_offset; + m_length = a.m_length; + m_data = (m_length > 0) ? new T[m_length] - m_offset : nullptr; } - for (int i = mindex; i <= maxdex; i++) { - data[i] = a.data[i]; + for (int i = m_offset; i <= last_index(); i++) { + m_data[i] = a.m_data[i]; } } return *this; } - //@} - - /// @name Operator overloading - //@{ - /// Test the equality of two arrays bool operator==(const Array &a) const { - if (mindex != a.mindex || maxdex != a.maxdex) { + if (m_offset != a.m_offset || m_length != a.m_length) { return false; } - for (int i = mindex; i <= maxdex; i++) { + for (int i = m_offset; i <= last_index(); i++) { if ((*this)[i] != a[i]) { return false; } @@ -191,143 +228,60 @@ template class Array { return true; } - /// Test the inequality of two arrays bool operator!=(const Array &a) const { return !(*this == a); } - //@} - - /// @name General data access - //@{ - /// Return the length of the array - int Length() const { return maxdex - mindex + 1; } - - /// Return the first index - int First() const { return mindex; } - - /// Return the last index - int Last() const { return maxdex; } - - /// Return a forward iterator starting at the beginning of the array - iterator begin() { return iterator(this, mindex); } - /// Return a forward iterator past the end of the array - iterator end() { return iterator(this, maxdex + 1); } - /// Return a const forward iterator starting at the beginning of the array - const_iterator begin() const { return const_iterator(this, mindex); } - /// Return a const forward iterator past the end of the array - const_iterator end() const { return const_iterator(this, maxdex + 1); } - /// Return a const forward iterator starting at the beginning of the array - const_iterator cbegin() const { return const_iterator(this, mindex); } - /// Return a const forward iterator past the end of the array - const_iterator cend() const { return const_iterator(this, maxdex + 1); } - - /// Access the index'th entry in the array + const T &operator[](int index) const { - if (index < mindex || index > maxdex) { + if (index < m_offset || index > last_index()) { throw IndexException(); } - return data[index]; + return m_data[index]; } - /// Access the index'th entry in the array T &operator[](int index) { - if (index < mindex || index > maxdex) { + if (index < m_offset || index > last_index()) { throw IndexException(); } - return data[index]; + return m_data[index]; } + const T &front() const { return m_data[m_offset]; } + T &front() { return m_data[m_offset]; } + const T &back() const { return m_data[last_index()]; } + T &back() { return m_data[last_index()]; } + + iterator begin() { return {this, m_offset}; } + const_iterator begin() const { return {this, m_offset}; } + iterator end() { return {this, m_offset + m_length}; } + const_iterator end() const { return {this, m_offset + m_length}; } + const_iterator cbegin() const { return {this, m_offset}; } + const_iterator cend() const { return {this, m_offset + m_length}; } + + bool empty() const { return this->m_length == 0; } + size_t size() const { return m_length; } + int first_index() const { return m_offset; } + int last_index() const { return m_offset + m_length - 1; } - /// Return the index at which a given element resides in the array. - int Find(const T &t) const - { - int i; - for (i = this->mindex; i <= this->maxdex && this->data[i] != t; i++) - ; - return (i <= this->maxdex) ? i : (mindex - 1); - } - - /// Return true if the element is currently residing in the array - bool Contains(const T &t) const { return Find(t) != mindex - 1; } - //@} - - /// @name Modifying the contents of the array - //@{ - /// \brief Insert a new element into the array at a given index. - /// - /// Insert a new element into the array at a given index. If the index is - /// less than the lowest index, the element is inserted at the beginning; - /// if the index is greater than the highest index, the element is appended. - /// Returns the index at which the element actually is placed. - int Insert(const T &t, int n) - { - return InsertAt(t, (n < this->mindex) ? this->mindex - : ((n > this->maxdex + 1) ? this->maxdex + 1 : n)); - } - - /// \brief Remove an element from the array. - /// - /// Remove the element at a given index from the array. Returns the value - /// of the element removed. - T Remove(int n) - { - if (n < this->mindex || n > this->maxdex) { - throw IndexException(); - } - - T ret(this->data[n]); - T *new_data = (--this->maxdex >= this->mindex) - ? new T[this->maxdex - this->mindex + 1] - this->mindex - : nullptr; - - int i; - for (i = this->mindex; i < n; i++) { - new_data[i] = this->data[i]; - } - for (; i <= this->maxdex; i++) { - new_data[i] = this->data[i + 1]; - } - - delete[] (this->data + this->mindex); - this->data = new_data; - - return ret; - } - //@} - - /// @name STL-style interface - /// - /// These operations are a partial implementation of operations on - /// STL-style list containers. It is suggested that future code be - /// written to use these, and existing code ported to use them as - /// possible. - ///@{ - /// Return whether the array container is empty (has size 0). - bool empty() const { return (this->maxdex < this->mindex); } - /// Return the number of elements in the array container. - size_t size() const { return maxdex - mindex + 1; } - /// Access first element. - const T &front() const { return data[mindex]; } - /// Access first element. - T &front() { return data[mindex]; } - /// Access last element. - const T &back() const { return data[maxdex]; } - /// Access last element. - T &back() { return data[maxdex]; } - - /// Adds a new element at the end of the array container, after its - /// current last element. - void push_back(const T &val) { InsertAt(val, this->maxdex + 1); } - /// Removes all elements from the array container (which are destroyed), - /// leaving the container with a size of 0. void clear() { - delete[] (this->data + this->mindex); - this->data = 0; - this->maxdex = this->mindex - 1; + if (m_length > 0) { + delete[] (m_data + m_offset); + } + m_data = nullptr; + m_length = 0; } - ///@} + void insert(const_iterator pos, const T &value) { InsertAt(value, pos.m_index); } + void erase(iterator pos) { Remove(pos.m_index); } + void push_back(const T &val) { InsertAt(val, this->last_index() + 1); } + void pop_back() { Remove(last_index()); } }; +/// Convenience function to erase the element at `p_index` +template void erase_atindex(Array &p_array, int p_index) +{ + p_array.erase(std::next(p_array.begin(), p_index - p_array.first_index())); +} + } // end namespace Gambit -#endif // LIBGAMBIT_ARRAY_H +#endif // GAMBIT_CORE_ARRAY_H diff --git a/src/core/pvector.cc b/src/core/core.h similarity index 72% rename from src/core/pvector.cc rename to src/core/core.h index 698d8e02d..726235445 100644 --- a/src/core/pvector.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/libgambit/pvector.cc -// Instantiation of partitioned vector types +// 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,9 +20,15 @@ // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // -#include "gambit.h" -#include "pvector.imp" +#ifndef GAMBIT_CORE_CORE_H +#define GAMBIT_CORE_CORE_H -template class Gambit::PVector; -template class Gambit::PVector; -template class Gambit::PVector; +#include "util.h" +#include "array.h" +#include "list.h" +#include "recarray.h" +#include "vector.h" +#include "matrix.h" +#include "rational.h" + +#endif // GAMBIT_CORE_CORE_H diff --git a/src/core/dvector.cc b/src/core/dvector.cc deleted file mode 100644 index 15d38be84..000000000 --- a/src/core/dvector.cc +++ /dev/null @@ -1,27 +0,0 @@ -// -// This file is part of Gambit -// Copyright (c) 1994-2025, The Gambit Project (https://www.gambit-project.org) -// -// FILE: src/libgambit/dvector.cc -// Instantiation of doubly partitioned vector types -// -// 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 "dvector.imp" - -template class Gambit::DVector; -template class Gambit::DVector; diff --git a/src/core/dvector.h b/src/core/dvector.h deleted file mode 100644 index 9d206b76f..000000000 --- a/src/core/dvector.h +++ /dev/null @@ -1,69 +0,0 @@ -// -// This file is part of Gambit -// Copyright (c) 1994-2025, The Gambit Project (https://www.gambit-project.org) -// -// FILE: src/libgambit/dvector.h -// Doubly-partitioned vector 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 LIBGAMBIT_DVECTOR_H -#define LIBGAMBIT_DVECTOR_H - -#include "pvector.h" - -namespace Gambit { - -template class DVector : public PVector { -private: - void setindex(); - -protected: - T ***dvptr; - Array dvlen, dvidx; - -public: - explicit DVector(const PVector &shape); - DVector(const DVector &v); - ~DVector() override; - - T &operator()(int a, int b, int c); - const T &operator()(int a, int b, int c) const; - - DVector &operator=(const DVector &v) - { - if (this == &v) { - return *this; - } - if (dvlen != v.dvlen || dvidx != v.dvidx) { - throw DimensionException(); - } - static_cast &>(*this) = static_cast &>(v); - return *this; - } - - DVector &operator=(const Vector &v) - { - static_cast &>(*this) = v; - return *this; - } - - DVector &operator=(const T &c); -}; - -} // end namespace Gambit - -#endif // LIBGAMBIT_DVECTOR_H diff --git a/src/core/dvector.imp b/src/core/dvector.imp deleted file mode 100644 index 209951a8c..000000000 --- a/src/core/dvector.imp +++ /dev/null @@ -1,115 +0,0 @@ -// -// This file is part of Gambit -// Copyright (c) 1994-2025, The Gambit Project (https://www.gambit-project.org) -// -// FILE: src/libgambit/dvector.imp -// Implementation of doubly-partitioned vector 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 "dvector.h" - -namespace Gambit { - -//-------------------------------------------------------------------------- -// DVector: Private and protected member functions -//-------------------------------------------------------------------------- - -template void DVector::setindex() -{ - int index = 1; - - for (int i = 1; i <= dvlen.Length(); i++) { - dvptr[i] = this->svptr + index - 1; - dvidx[i] = index; - index += dvlen[i]; - } -} - -//-------------------------------------------------------------------------- -// DVector: Constructors, destructor, and constructive operators -//-------------------------------------------------------------------------- - -template -DVector::DVector(const PVector &shape) - : PVector(static_cast &>(shape)), dvlen(shape.Lengths().Length()), - dvidx(shape.Lengths().Length()) -{ - dvptr = new T **[dvlen.Length()]; - dvptr -= 1; - - for (int i = 1; i <= dvlen.Length(); i++) { - dvlen[i] = shape.Lengths()[i]; - } - - setindex(); -} - -template -DVector::DVector(const DVector &v) : PVector(v), dvlen(v.dvlen), dvidx(v.dvidx) -{ - dvptr = new T **[dvlen.Length()]; - dvptr -= 1; - - setindex(); -} - -template DVector::~DVector() -{ - if (dvptr) { - delete[] (dvptr + 1); - } -} - -template DVector &DVector::operator=(const T &c) -{ - PVector::operator=(c); - return *this; -} - -//-------------------------------------------------------------------------- -// DVector: Operator definitions -//-------------------------------------------------------------------------- - -template T &DVector::operator()(int a, int b, int c) -{ - if (dvlen.First() > a || a > dvlen.Last()) { - throw IndexException(); - } - if (1 > b || b > dvlen[a]) { - throw IndexException(); - } - if (1 > c || c > this->svlen[dvidx[a] + b - 1]) { - throw IndexException(); - } - return dvptr[a][b][c]; -} - -template const T &DVector::operator()(int a, int b, int c) const -{ - if (dvlen.First() > a || a > dvlen.Last()) { - throw IndexException(); - } - if (1 > b || b > dvlen[a]) { - throw IndexException(); - } - if (1 > c || c > this->svlen[dvidx[a] + b - 1]) { - throw IndexException(); - } - return dvptr[a][b][c]; -} - -} // end namespace Gambit diff --git a/src/core/function.cc b/src/core/function.cc index 2dcd72bf6..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.Length(); 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.Length(); 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; @@ -330,7 +333,7 @@ bool ConjugatePRMinimizer::Iterate(const Function &fdf, Vector &x, doubl x = x2; /* Choose a new conjugate direction for the next step */ - iter = (iter + 1) % x.Length(); + iter = (iter + 1) % x.size(); if (iter == 0) { p = gradient; 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 53e675413..64d6718a4 100644 --- a/src/core/list.h +++ b/src/core/list.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/libgambit/list.h -// A generic (doubly) linked-list container class +// FILE: src/core/list.h +// A generic linked-list container 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 @@ -20,436 +20,113 @@ // 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 namespace Gambit { -/// A doubly-linked list container. +/// @brief A linked-list container +/// +/// This is a lightweight wrapper around std::list. It exists as a transitional +/// class between Gambit's legacy linked-list implementation Gambit::List and +/// STL's list. /// -/// This implements a doubly-linked list. A special feature of this -/// class is that it caches the last item accessed by indexing via -/// operator[], meaning that, if accesses are done in sequential order, -/// indexing time is constant. +/// Gambit's List container supported the index operator [], which is not defined +/// on STL lists. Therefore this class provides such an operator by wrapping +/// an appropriate iterator-based operation. This is very inefficient! However, +/// the [] operator and indexing is tightly embedded in the remaining uses of this +/// class, so this operator provides a refactoring path while the remaining uses +/// are rewritten. /// -/// This index-cacheing feature was implemented very early in the development -/// of Gambit, before STL-like concepts of iterators had been fully -/// developed. An iterator approach is a better and more robust solution -/// to iterating lists, both in terms of performance and encapsulation. -/// Therefore, it is recommended to avoid operator[] and use the provided -/// iterator classes in new code, and to upgrade existing code to the -/// iterator idiom as practical. +/// Note importantly that the index operator [] is **1-based** - that is, the first +/// element of the list is 1, not 0. template class List { -protected: - class Node { - public: - T m_data; - Node *m_prev, *m_next; - - // CONSTRUCTOR - Node(const T &p_data, Node *p_prev, Node *p_next); - }; - - int m_length; - Node *m_head, *m_tail; - - int m_currentIndex; - Node *m_currentNode; - - int InsertAt(const T &t, int where); +private: + std::list m_list; public: - class iterator { - private: - List &m_list; - Node *m_node; + using iterator = typename std::list::iterator; + using const_iterator = typename std::list::const_iterator; + using value_type = typename std::list::value_type; + using size_type = typename std::list::size_type; - public: - iterator(List &p_list, Node *p_node) : m_list(p_list), m_node(p_node) {} - T &operator*() { return m_node->m_data; } - iterator &operator++() - { - m_node = m_node->m_next; - return *this; - } - bool operator==(const iterator &it) const { return (m_node == it.m_node); } - bool operator!=(const iterator &it) const { return (m_node != it.m_node); } - }; + List() = default; + List(const List &) = default; + ~List() = default; - class const_iterator { - private: - const List &m_list; - Node *m_node; + List &operator=(const List &) = default; - public: - const_iterator(const List &p_list, Node *p_node) : m_list(p_list), m_node(p_node) {} - const T &operator*() const { return m_node->m_data; } - const_iterator &operator++() - { - m_node = m_node->m_next; - return *this; + const T &operator[](size_type p_index) const + { + if (p_index < 1 || p_index > m_list.size()) { + throw IndexException(); } - bool operator==(const const_iterator &it) const { return (m_node == it.m_node); } - bool operator!=(const const_iterator &it) const { return (m_node != it.m_node); } - }; - - List(); - List(const List &); - virtual ~List(); - - List &operator=(const List &); - - bool operator==(const List &b) const; - bool operator!=(const List &b) const; - - /// Return a forward iterator starting at the beginning of the list - iterator begin() { return iterator(*this, m_head); } - /// Return a forward iterator past the end of the list - iterator end() { return iterator(*this, nullptr); } - /// Return a const forward iterator starting at the beginning of the list - const_iterator begin() const { return const_iterator(*this, m_head); } - /// Return a const forward iterator past the end of the list - const_iterator end() const { return const_iterator(*this, nullptr); } - /// Return a const forward iterator starting at the beginning of the list - const_iterator cbegin() const { return const_iterator(*this, m_head); } - /// Return a const forward iterator past the end of the list - const_iterator cend() const { return const_iterator(*this, nullptr); } - - const T &operator[](int) const; - T &operator[](int); - - List operator+(const List &b) const; - List &operator+=(const List &b); - - int Insert(const T &, int); - T Remove(int); + return *std::next(m_list.cbegin(), p_index - 1); + } - int Find(const T &) const; - bool Contains(const T &t) const; - int Length() const { return m_length; } + T &operator[](size_type p_index) + { + if (p_index < 1 || p_index > m_list.size()) { + throw IndexException(); + } + return *std::next(m_list.begin(), p_index - 1); + } /// @name STL-style interface /// - /// These operations are a partial implementation of operations on - /// STL-style list containers. It is suggested that future code be - /// written to use these, and existing code ported to use them as - /// possible. + /// These operations forward STL-type operations to the underlying list. + /// This does not provide all operations on std::list, only ones used in + /// existing code. Rather than adding new functions here, existing code + /// should be rewritten to use std::list directly. ///@{ /// Return whether the list container is empty (has size 0). - bool empty() const { return (m_length == 0); } + bool empty() const { return m_list.empty(); } /// Return the number of elements in the list container. - size_t size() const { return m_length; } + size_type size() const { return m_list.size(); } + /// Adds a new element at the beginning of the list container + void push_front(const T &val) { m_list.push_front(val); } /// Adds a new element at the end of the list container, after its /// current last element. - void push_back(const T &val); + void push_back(const T &val) { m_list.push_back(val); } + /// Removes the last element + void pop_back() { m_list.pop_back(); } + /// Inserts the value at the specified position + void insert(iterator pos, const T &value) { m_list.insert(pos, value); } + /// Erases the element at the specified position + void erase(iterator pos) { m_list.erase(pos); } + /// Removes all elements from the list container (which are destroyed), /// leaving the container with a size of 0. - void clear(); - /// Returns a reference to the first elemnet in the list container. - T &front() { return m_head->m_data; } + void clear() { m_list.clear(); } + /// Returns a reference to the first element in the list container. + T &front() { return m_list.front(); } /// Returns a reference to the first element in the list container. - const T &front() const { return m_head->m_data; } + const T &front() const { return m_list.front(); } /// Returns a reference to the last element in the list container. - T &back() { return m_tail->m_data; } + T &back() { return m_list.back(); } /// Returns a reference to the last element in the list container. - const T &back() const { return m_tail->m_data; } - ///@} -}; - -//-------------------------------------------------------------------------- -// Node: Member function implementations -//-------------------------------------------------------------------------- - -template -List::Node::Node(const T &p_data, typename List::Node *p_prev, - typename List::Node *p_next) - : m_data(p_data), m_prev(p_prev), m_next(p_next) -{ -} - -//-------------------------------------------------------------------------- -// List: Member function implementations -//-------------------------------------------------------------------------- - -template -List::List() - : m_length(0), m_head(nullptr), m_tail(nullptr), m_currentIndex(0), m_currentNode(nullptr) -{ -} - -template List::List(const List &b) : m_length(b.m_length) -{ - if (m_length) { - Node *n = b.m_head; - m_head = new Node(n->m_data, nullptr, nullptr); - n = n->m_next; - m_tail = m_head; - while (n) { - m_tail->m_next = new Node(n->m_data, m_tail, nullptr); - n = n->m_next; - m_tail = m_tail->m_next; - } - m_currentIndex = 1; - m_currentNode = m_head; - } - else { - m_head = m_tail = nullptr; - m_currentIndex = 0; - m_currentNode = nullptr; - } -} - -template List::~List() -{ - Node *n = m_head; - while (n) { - Node *m_next = n->m_next; - delete n; - n = m_next; - } -} - -template int List::InsertAt(const T &t, int num) -{ - if (num < 1 || num > m_length + 1) { - throw IndexException(); - } - - if (!m_length) { - m_head = m_tail = new Node(t, nullptr, nullptr); - m_length = 1; - m_currentIndex = 1; - m_currentNode = m_head; - return m_length; - } - - Node *n; - int i; - - if (num <= 1) { - n = new Node(t, nullptr, m_head); - m_head->m_prev = n; - m_currentNode = m_head = n; - m_currentIndex = 1; - } - else if (num >= m_length + 1) { - n = new Node(t, m_tail, nullptr); - m_tail->m_next = n; - m_currentNode = m_tail = n; - m_currentIndex = m_length + 1; - } - else { - if (num < m_currentIndex) { - for (i = m_currentIndex, n = m_currentNode; i > num; i--, n = n->m_prev) - ; - } - else { - for (i = m_currentIndex, n = m_currentNode; i < num; i++, n = n->m_next) - ; - } - n = new Node(t, n->m_prev, n); - m_currentNode = n->m_prev->m_next = n->m_next->m_prev = n; - m_currentIndex = num; - } - - m_length++; - return num; -} - -//--------------------- visible functions ------------------------ - -template List &List::operator=(const List &b) -{ - if (this != &b) { - Node *n = m_head; - while (n) { - Node *m_next = n->m_next; - delete n; - n = m_next; - } - - m_length = b.m_length; - m_currentIndex = b.m_currentIndex; - if (m_length) { - Node *n = b.m_head; - m_head = new Node(n->m_data, nullptr, nullptr); - if (b.m_currentNode == n) { - m_currentNode = m_head; - } - n = n->m_next; - m_tail = m_head; - while (n) { - m_tail->m_next = new Node(n->m_data, m_tail, nullptr); - if (b.m_currentNode == n) { - m_currentNode = m_tail->m_next; - } - n = n->m_next; - m_tail = m_tail->m_next; - } - } - else { - m_head = m_tail = nullptr; - } - } - return *this; -} - -template bool List::operator==(const List &b) const -{ - if (m_length != b.m_length) { - return false; - } - for (Node *m = m_head, *n = b.m_head; m; m = m->m_next, n = n->m_next) { - if (m->m_data != n->m_data) { - return false; - } - } - return true; -} - -template bool List::operator!=(const List &b) const { return !(*this == b); } - -template const T &List::operator[](int num) const -{ - if (num < 1 || num > m_length) { - throw IndexException(); - } - - int i; - Node *n; - if (num < m_currentIndex) { - for (i = m_currentIndex, n = m_currentNode; i > num; i--, n = n->m_prev) - ; - } - else { - for (i = m_currentIndex, n = m_currentNode; i < num; i++, n = n->m_next) - ; - } - return n->m_data; -} + const T &back() const { return m_list.back(); } -template T &List::operator[](int num) -{ - if (num < 1 || num > m_length) { - throw IndexException(); - } - Node *n; - int i; - if (num < m_currentIndex) { - for (i = m_currentIndex, n = m_currentNode; i > num; i--, n = n->m_prev) - ; - } - else { - for (i = m_currentIndex, n = m_currentNode; i < num; i++, n = n->m_next) - ; - } - m_currentIndex = i; - m_currentNode = n; - return n->m_data; -} - -template List List::operator+(const List &b) const -{ - List result(*this); - Node *n = b.m_head; - while (n) { - result.Append(n->data); - n = n->m_next; - } - return result; -} - -template List &List::operator+=(const List &b) -{ - Node *n = b.m_head; - - while (n) { - push_back(n->m_data); - n = n->m_next; - } - return *this; -} - -template int List::Insert(const T &t, int n) -{ - return InsertAt(t, (n < 1) ? 1 : ((n > m_length + 1) ? m_length + 1 : n)); -} - -template T List::Remove(int num) -{ - if (num < 1 || num > m_length) { - throw IndexException(); - } - Node *n; - int i; + bool operator==(const List &b) const { return m_list == b.m_list; } + bool operator!=(const List &b) const { return m_list != b.m_list; } - if (num < m_currentIndex) { - for (i = m_currentIndex, n = m_currentNode; i > num; i--, n = n->m_prev) - ; - } - else { - for (i = m_currentIndex, n = m_currentNode; i < num; i++, n = n->m_next) - ; - } - - if (n->m_prev) { - n->m_prev->m_next = n->m_next; - } - else { - m_head = n->m_next; - } - if (n->m_next) { - n->m_next->m_prev = n->m_prev; - } - else { - m_tail = n->m_prev; - } - - m_length--; - m_currentIndex = i; - m_currentNode = n->m_next; - if (m_currentIndex > m_length) { - m_currentIndex = m_length; - m_currentNode = m_tail; - } - T ret = n->m_data; - delete n; - return ret; -} - -template int List::Find(const T &t) const -{ - if (m_length == 0) { - return 0; - } - Node *n = m_head; - for (int i = 1; n; i++, n = n->m_next) { - if (n->m_data == t) { - return i; - } - } - return 0; -} - -template bool List::Contains(const T &t) const { return (Find(t) != 0); } - -template void List::push_back(const T &val) { InsertAt(val, m_length + 1); } - -template void List::clear() -{ - Node *n = m_head; - while (n) { - Node *m_next = n->m_next; - delete n; - n = m_next; - } - m_length = 0; - m_head = nullptr; - m_tail = nullptr; - m_currentIndex = 0; - m_currentNode = nullptr; -} + /// Return a forward iterator starting at the beginning of the list + iterator begin() { return m_list.begin(); } + /// Return a forward iterator past the end of the list + iterator end() { return m_list.end(); } + /// Return a const forward iterator starting at the beginning of the list + const_iterator begin() const { return m_list.begin(); } + /// Return a const forward iterator past the end of the list + const_iterator end() const { return m_list.end(); } + /// Return a const forward iterator starting at the beginning of the list + const_iterator cbegin() const { return m_list.cbegin(); } + /// Return a const forward iterator past the end of the list + const_iterator cend() const { return m_list.cend(); } + ///@} +}; } // 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 ace3be371..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; @@ -169,7 +169,7 @@ template void Matrix::CMultiply(const Vector &in, Vector &out T sum = (T)0; T *src1 = this->data[i] + this->mincol; - T *src2 = in.data + this->mincol; + auto src2 = in.begin(); int j = this->maxcol - this->mincol + 1; while (j--) { sum += *(src1++) * *(src2++); @@ -217,7 +217,7 @@ template void Matrix::RMultiply(const Vector &in, Vector &out T k = in[i]; T *src = this->data[i] + this->mincol; - T *dst = out.data + this->mincol; + auto dst = out.begin(); int j = this->maxcol - this->mincol + 1; while (j--) { *(dst++) += *(src++) * k; @@ -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/pvector.h b/src/core/pvector.h deleted file mode 100644 index 1b6233346..000000000 --- a/src/core/pvector.h +++ /dev/null @@ -1,85 +0,0 @@ -// -// This file is part of Gambit -// Copyright (c) 1994-2025, The Gambit Project (https://www.gambit-project.org) -// -// FILE: src/libgambit/pvector.h -// Partitioned vector 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 LIBGAMBIT_PVECTOR_H -#define LIBGAMBIT_PVECTOR_H - -#include "vector.h" - -namespace Gambit { - -template class PVector : public Vector { -private: - int sum(const Array &V) const; - void setindex(); - -protected: - T **svptr; - Array svlen; - - int Check(const PVector &v) const; - -public: - // constructors - - PVector(); - explicit PVector(const Array &sig); - PVector(const Vector &val, const Array &sig); - PVector(const PVector &v); - ~PVector() override; - - // element access operators - T &operator()(int a, int b); - const T &operator()(int a, int b) const; - - // extract a subvector - Vector GetRow(int row) const; - void GetRow(int row, Vector &v) const; - void SetRow(int row, const Vector &v); - void CopyRow(int row, const PVector &v); - - // more operators - - PVector &operator=(const PVector &v); - PVector &operator=(const Vector &v); - PVector &operator=(T c); - - PVector operator+(const PVector &v) const; - PVector &operator+=(const PVector &v); - PVector operator-() const; - PVector operator-(const PVector &v) const; - PVector &operator-=(const PVector &v); - T operator*(const PVector &v) const; - PVector operator*(const T &c) const; - PVector &operator*=(const T &c); - PVector operator/(T c); - - bool operator==(const PVector &v) const; - bool operator!=(const PVector &v) const; - - // parameter access functions - const Array &Lengths() const; -}; - -} // end namespace Gambit - -#endif // LIBGAMBIT_PVECTOR_H diff --git a/src/core/pvector.imp b/src/core/pvector.imp deleted file mode 100644 index e1bdce227..000000000 --- a/src/core/pvector.imp +++ /dev/null @@ -1,297 +0,0 @@ -// -// This file is part of Gambit -// Copyright (c) 1994-2025, The Gambit Project (https://www.gambit-project.org) -// -// FILE: src/libgambit/pvector.imp -// Implementation of partitioned vector members -// -// 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 "pvector.h" - -namespace Gambit { - -//------------------------------------------------------------------------- -// PVector: Private and protected member functions -//------------------------------------------------------------------------- - -template int PVector::sum(const Array &V) const -{ - int total = 0; - for (int i = V.First(); i <= V.Last(); total += V[i++]) - ; - return total; -} - -template void PVector::setindex() -{ - int index = this->First(); - for (int i = 1; i <= svlen.Length(); i++) { - svptr[i] = this->data + index - 1; - index += svlen[i]; - } - // assert(index == this->Last() + 1); -} - -template int PVector::Check(const PVector &v) const -{ - if (v.mindex == this->mindex && v.maxdex == this->maxdex) { - for (int i = 1; i <= svlen.Length(); i++) { - if (svlen[i] != v.svlen[i]) { - return 0; - } - } - return 1; - } - return 0; -} - -//------------------------------------------------------------------------- -// PVector: Constructors, destructor, and constructive operators -//------------------------------------------------------------------------- - -template PVector::PVector() : svptr(nullptr) {} - -template PVector::PVector(const Array &sig) : Vector(sum(sig)), svlen(sig) -{ - svptr = new T *[sig.Last() - sig.First() + 1]; - svptr -= 1; // align things correctly - setindex(); -} - -template -PVector::PVector(const Vector &val, const Array &sig) : Vector(val), svlen(sig) -{ - // assert(sum(svlen) == val.Length()); - svptr = new T *[sig.Last() - sig.First() + 1]; - svptr -= 1; - setindex(); -} - -template PVector::PVector(const PVector &v) : Vector(v), svlen(v.svlen) -{ - svptr = new T *[v.svlen.Last() - v.svlen.First() + 1]; - svptr -= 1; - setindex(); -} - -template PVector::~PVector() -{ - if (svptr) { - delete[] (svptr + 1); - } -} - -template PVector &PVector::operator=(const PVector &v) -{ - if (!Check(v)) { - throw DimensionException(); - } - Vector::operator=(v); - return (*this); -} - -template PVector &PVector::operator=(const Vector &v) -{ - Vector::operator=(v); - return (*this); -} - -template PVector &PVector::operator=(T c) -{ - Vector::operator=(c); - return (*this); -} - -//------------------------------------------------------------------------- -// PVector: Operator definitions -//------------------------------------------------------------------------- - -template T &PVector::operator()(int a, int b) -{ - if (svlen.First() > a || a > svlen.Last()) { - throw IndexException(); - } - if (1 > b || b > svlen[a]) { - throw IndexException(); - } - - return svptr[a][b]; -} - -template const T &PVector::operator()(int a, int b) const -{ - if (svlen.First() > a || a > svlen.Last()) { - throw IndexException(); - } - if (1 > b || b > svlen[a]) { - throw IndexException(); - } - return svptr[a][b]; -} - -template PVector PVector::operator+(const PVector &v) const -{ - if (!Check(v)) { - throw DimensionException(); - } - PVector tmp(*this); - tmp.Vector::operator+=(v); - return tmp; -} - -template PVector &PVector::operator+=(const PVector &v) -{ - if (!Check(v)) { - throw DimensionException(); - } - Vector::operator+=(v); - return (*this); -} - -template PVector PVector::operator-() const -{ - PVector tmp(*this); - for (int i = this->First(); i <= this->Last(); i++) { - tmp[i] = -tmp[i]; - } - return tmp; -} - -template PVector PVector::operator-(const PVector &v) const -{ - if (!Check(v)) { - throw DimensionException(); - } - PVector tmp(*this); - tmp.Vector::operator-=(v); - return tmp; -} - -template PVector &PVector::operator-=(const PVector &v) -{ - if (!Check(v)) { - throw DimensionException(); - } - Vector::operator-=(v); - return (*this); -} - -template T PVector::operator*(const PVector &v) const -{ - if (!Check(v)) { - throw DimensionException(); - } - return (*this).Vector::operator*(v); -} - -template PVector PVector::operator*(const T &c) const -{ - PVector ret(*this); - ret *= c; - return ret; -} - -template PVector &PVector::operator*=(const T &c) -{ - Vector::operator*=(c); - return (*this); -} - -template PVector PVector::operator/(T c) -{ - PVector tmp(*this); - tmp = tmp.Vector::operator/(c); - return tmp; -} - -template bool PVector::operator==(const PVector &v) const -{ - if (!Check(v)) { - throw DimensionException(); - } - return (*this).Vector::operator==(v); -} - -template bool PVector::operator!=(const PVector &v) const -{ - return !((*this) == v); -} - -//------------------------------------------------------------------------- -// PVector: General data access -//------------------------------------------------------------------------- - -template Vector PVector::GetRow(int row) const -{ - if (svlen.First() > row || row > svlen.Last()) { - throw IndexException(); - } - - Vector v(1, svlen[row]); - - for (int i = v.First(); i <= v.Last(); i++) { - v[i] = (*this)(row, i); - } - return v; -} - -template void PVector::GetRow(int row, Vector &v) const -{ - if (svlen.First() > row || row > svlen.Last()) { - throw IndexException(); - } - if (v.First() != 1 || v.Last() != svlen[row]) { - throw DimensionException(); - } - - for (int i = v.First(); i <= v.Last(); i++) { - v[i] = (*this)(row, i); - } -} - -template void PVector::SetRow(int row, const Vector &v) -{ - if (svlen.First() > row || row > svlen.Last()) { - throw IndexException(); - } - if (v.First() != 1 || v.Last() != svlen[row]) { - throw DimensionException(); - } - - for (int i = v.First(); i <= v.Last(); i++) { - (*this)(row, i) = v[i]; - } -} - -template void PVector::CopyRow(int row, const PVector &v) -{ - if (!Check(v)) { - throw DimensionException(); - } - - if (svlen.First() > row || row > svlen.Last()) { - throw IndexException(); - } - - for (int i = 1; i <= svlen[row]; i++) { - svptr[row][i] = v.svptr[row][i]; - } -} - -template const Array &PVector::Lengths() const { return svlen; } - -} // end namespace Gambit diff --git a/src/core/rational.cc b/src/core/rational.cc index 0895cf6ef..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); } @@ -401,6 +401,8 @@ Rational::Rational(long n) : num(n), den(&OneRep) {} Rational::Rational(int n) : num(n), den(&OneRep) {} +Rational::Rational(size_t n) : num(int(n)), den(&OneRep) {} + Rational::Rational(long n, long d) : num(n), den(d) { if (d == 0) { @@ -437,8 +439,6 @@ bool Rational::operator>(const Rational &y) const { return compare(*this, y) > 0 bool Rational::operator>=(const Rational &y) const { return compare(*this, y) >= 0; } -int sign(const Rational &x) { return sign(x.num); } - void Rational::negate() { num.negate(); } Rational &Rational::operator+=(const Rational &y) diff --git a/src/core/rational.h b/src/core/rational.h index 529c942c5..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 @@ -44,6 +45,7 @@ class Rational { Rational(); explicit Rational(double); explicit Rational(int); + explicit Rational(size_t); explicit Rational(long n); Rational(int n, int d); Rational(long n, long d); @@ -115,6 +117,8 @@ class Rational { // Naming compatible with Boost's lexical_cast concept for potential future compatibility. template <> Rational lexical_cast(const std::string &); +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 d4e087d6d..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() == mincol && v.Last() == maxcol); -} - -template bool RectArray::CheckColumn(int col) const -{ - return (mincol <= col && col <= maxcol); -} - -template bool RectArray::CheckColumn(const Array &v) const -{ - return (v.First() == minrow && v.Last() == 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.cc b/src/core/vector.cc deleted file mode 100644 index 100f637e4..000000000 --- a/src/core/vector.cc +++ /dev/null @@ -1,30 +0,0 @@ -// -// This file is part of Gambit -// Copyright (c) 1994-2025, The Gambit Project (https://www.gambit-project.org) -// -// FILE: src/libgambit/vector.cc -// Instantiation of vector types -// -// 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 "vector.imp" - -template class Gambit::Vector; -template class Gambit::Vector; -template class Gambit::Vector; -template class Gambit::Vector; -template class Gambit::Vector; diff --git a/src/core/vector.h b/src/core/vector.h index 64a7e4aa7..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,8 +20,12 @@ // 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 { @@ -29,7 +33,12 @@ template class Matrix; /// A mathematical vector: a list of numbers with the standard math operators template class Vector : public Array { - friend class Matrix; +private: + // check vector for identical boundaries + bool Check(const Vector &v) const + { + return (v.first_index() == this->first_index() && v.size() == this->size()); + } public: /** Create a vector of length len, starting at 1 */ @@ -42,37 +51,119 @@ template class Vector : public Array { ~Vector() override = default; /** Assignment operator: requires vectors to be of same length */ - Vector &operator=(const Vector &V); + Vector &operator=(const Vector &V) + { + if (!Check(V)) { + throw DimensionException(); + } + Array::operator=(V); + return *this; + } /** Assigns the value c to all components of the vector */ - Vector &operator=(T c); - - Vector operator+(const Vector &V) const; - Vector &operator+=(const Vector &V); - - Vector operator-(); - Vector operator-(const Vector &V) const; - Vector &operator-=(const Vector &V); - - Vector operator*(T c) const; - Vector &operator*=(T c); - T operator*(const Vector &V) const; - - Vector operator/(T c) const; - - bool operator==(const Vector &V) const; + Vector &operator=(const T &c) + { + std::fill(this->begin(), this->end(), c); + return *this; + } + + Vector operator+(const Vector &V) const + { + if (!Check(V)) { + throw DimensionException(); + } + Vector tmp(this->first_index(), this->last_index()); + std::transform(this->cbegin(), this->cend(), V.cbegin(), tmp.begin(), std::plus<>()); + return tmp; + } + + Vector &operator+=(const Vector &V) + { + if (!Check(V)) { + throw DimensionException(); + } + std::transform(this->cbegin(), this->cend(), V.cbegin(), this->begin(), std::plus<>()); + return *this; + } + + Vector operator-(const Vector &V) const + { + if (!Check(V)) { + throw DimensionException(); + } + Vector tmp(this->first_index(), this->last_index()); + std::transform(this->cbegin(), this->cend(), V.cbegin(), tmp.begin(), std::minus<>()); + return tmp; + } + + Vector &operator-=(const Vector &V) + { + if (!Check(V)) { + throw DimensionException(); + } + std::transform(this->cbegin(), this->cend(), V.cbegin(), this->begin(), std::minus<>()); + return *this; + } + + Vector operator*(const T &c) const + { + Vector tmp(this->first_index(), this->last_index()); + std::transform(this->cbegin(), this->cend(), tmp.begin(), [&](const T &v) { return v * c; }); + return tmp; + } + + Vector &operator*=(const T &c) + { + std::transform(this->cbegin(), this->cend(), this->begin(), [&](const T &v) { return v * c; }); + return *this; + } + + T operator*(const Vector &V) const + { + if (!Check(V)) { + throw DimensionException(); + } + return std::inner_product(this->begin(), this->end(), V.begin(), static_cast(0)); + } + + Vector operator/(const T &c) const + { + Vector tmp(this->first_index(), this->last_index()); + std::transform(this->cbegin(), this->cend(), tmp.begin(), [&](const T &v) { return v / c; }); + return tmp; + } + + Vector &operator/=(const T &c) + { + std::transform(this->cbegin(), this->cend(), this->begin(), [&](const T &v) { return v / c; }); + return *this; + } + + bool operator==(const Vector &V) const + { + if (!Check(V)) { + throw DimensionException(); + } + return Array::operator==(V); + } bool operator!=(const Vector &V) const { return !(*this == V); } /** Tests if all components of the vector are equal to a constant c */ - bool operator==(T c) const; - bool operator!=(T c) const; + bool operator==(const T &c) const + { + return std::all_of(this->begin(), this->end(), [&](const T &v) { return v == c; }); + } + bool operator!=(const T &c) const + { + return std::any_of(this->begin(), this->end(), [&](const T &v) { return v != c; }); + } // square of length - T NormSquared() const; - - // check vector for identical boundaries - bool Check(const Vector &v) const; + T NormSquared() const + { + return std::inner_product(this->begin(), this->end(), this->begin(), static_cast(0)); + } }; } // end namespace Gambit -#endif // LIBGAMBIT_VECTOR_H +#endif // GAMBIT_CORE_VECTOR_H diff --git a/src/core/vector.imp b/src/core/vector.imp deleted file mode 100644 index 58064c63d..000000000 --- a/src/core/vector.imp +++ /dev/null @@ -1,199 +0,0 @@ -// -// This file is part of Gambit -// Copyright (c) 1994-2025, The Gambit Project (https://www.gambit-project.org) -// -// FILE: src/libgambit/vector.imp -// Implementation of vector 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 "vector.h" - -namespace Gambit { - -//------------------------------------------------------------------------ -// Vector: Constructors, destructor, constructive operators -//------------------------------------------------------------------------ - -template Vector &Vector::operator=(const Vector &V) -{ - if (!Check(V)) { - throw DimensionException(); - } - Array::operator=(V); - return *this; -} - -//------------------------------------------------------------------------ -// inline arithmetic operators -//------------------------------------------------------------------------ - -template bool Vector::operator!=(T c) const { return !(*this == c); } - -//------------------------------------------------------------------------ -// inline internal functions -//------------------------------------------------------------------------ - -template bool Vector::Check(const Vector &v) const -{ - return (v.mindex == this->mindex && v.maxdex == this->maxdex); -} - -//------------------------------------------------------------------------ -// Vector: arithmetic operators -//------------------------------------------------------------------------ - -template Vector &Vector::operator=(T c) -{ - for (int i = this->mindex; i <= this->maxdex; i++) { - (*this)[i] = c; - } - return (*this); -} - -template bool Vector::operator==(const Vector &V) const -{ - if (!Check(V)) { - throw DimensionException(); - } - - for (int i = this->mindex; i <= this->maxdex; i++) { - if ((*this)[i] != V[i]) { - return false; - } - } - return true; -} - -// arithmetic operators -template Vector Vector::operator+(const Vector &V) const -{ - if (!Check(V)) { - throw DimensionException(); - } - - Vector tmp(this->mindex, this->maxdex); - for (int i = this->mindex; i <= this->maxdex; i++) { - tmp[i] = (*this)[i] + V[i]; - } - return tmp; -} - -template Vector Vector::operator-(const Vector &V) const -{ - if (!Check(V)) { - throw DimensionException(); - } - - Vector tmp(this->mindex, this->maxdex); - for (int i = this->mindex; i <= this->maxdex; i++) { - tmp[i] = (*this)[i] - V[i]; - } - return tmp; -} - -template Vector &Vector::operator+=(const Vector &V) -{ - if (!Check(V)) { - throw DimensionException(); - } - - for (int i = this->mindex; i <= this->maxdex; i++) { - (*this)[i] += V[i]; - } - return (*this); -} - -template Vector &Vector::operator-=(const Vector &V) -{ - if (!Check(V)) { - throw DimensionException(); - } - - for (int i = this->mindex; i <= this->maxdex; i++) { - (*this)[i] -= V[i]; - } - return (*this); -} - -template Vector Vector::operator-() -{ - Vector tmp(this->mindex, this->maxdex); - for (int i = this->mindex; i <= this->maxdex; i++) { - tmp[i] = -(*this)[i]; - } - return tmp; -} - -template Vector Vector::operator*(T c) const -{ - Vector tmp(this->mindex, this->maxdex); - for (int i = this->mindex; i <= this->maxdex; i++) { - tmp[i] = (*this)[i] * c; - } - return tmp; -} - -template Vector &Vector::operator*=(T c) -{ - for (int i = this->mindex; i <= this->maxdex; i++) { - (*this)[i] *= c; - } - return (*this); -} - -template T Vector::operator*(const Vector &V) const -{ - if (!Check(V)) { - throw DimensionException(); - } - - T sum = (T)0; - for (int i = this->mindex; i <= this->maxdex; i++) { - sum += (*this)[i] * V[i]; - } - return sum; -} - -template Vector Vector::operator/(T c) const -{ - Vector tmp(this->mindex, this->maxdex); - for (int i = this->mindex; i <= this->maxdex; i++) { - tmp[i] = (*this)[i] / c; - } - return tmp; -} - -template bool Vector::operator==(T c) const -{ - for (int i = this->mindex; i <= this->maxdex; i++) { - if ((*this)[i] != c) { - return false; - } - } - return true; -} - -template T Vector::NormSquared() const -{ - T answer = (T)0; - for (int i = 1; i <= this->Length(); i++) { - answer += (*this)[i] * (*this)[i]; - } - return answer; -} - -} // end namespace Gambit diff --git a/src/gambit.h b/src/gambit.h index 47808e595..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,121 +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 - -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 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" @@ -147,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 b2793054e..43c6d3543 100644 --- a/src/games/behavmixed.cc +++ b/src/games/behavmixed.cc @@ -35,17 +35,38 @@ namespace Gambit { template MixedBehaviorProfile::MixedBehaviorProfile(const Game &p_game) - : m_probs(p_game->NumActions()), m_support(BehaviorSupportProfile(p_game)), + : m_probs(p_game->BehavProfileLength()), m_support(BehaviorSupportProfile(p_game)), m_gameversion(p_game->GetVersion()) { + int index = 1; + for (const auto &player : p_game->GetPlayers()) { + for (const auto &infoset : player->GetInfosets()) { + for (const auto &action : infoset->GetActions()) { + m_profileIndex[action] = index++; + } + } + } SetCentroid(); } template MixedBehaviorProfile::MixedBehaviorProfile(const BehaviorSupportProfile &p_support) - : m_probs(p_support.NumActions()), m_support(p_support), + : m_probs(p_support.BehaviorProfileLength()), m_support(p_support), m_gameversion(p_support.GetGame()->GetVersion()) { + int index = 1; + for (const auto &player : p_support.GetGame()->GetPlayers()) { + for (const auto &infoset : player->GetInfosets()) { + for (const auto &action : infoset->GetActions()) { + if (p_support.Contains(action)) { + m_profileIndex[action] = index++; + } + else { + m_profileIndex[action] = -1; + } + } + } + } SetCentroid(); } @@ -67,16 +88,15 @@ 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, + GameNodeRep *node, std::map &map_nvals, std::map &map_bvals) { T prob; - for (int i = 1; i <= node->children.Length(); 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 (actions[node->GetInfoset()->GetNumber()] == static_cast(i)) { prob = T(1); } else { @@ -84,7 +104,7 @@ void MixedBehaviorProfile::RealizationProbs(const MixedStrategyProfile &mp } } else if (GetSupport().Contains(node->GetInfoset()->GetAction(i))) { - int num_actions = GetSupport().NumActions(node->GetInfoset()); + const int num_actions = GetSupport().GetActions(node->GetInfoset()).size(); prob = T(1) / T(num_actions); } else { @@ -92,10 +112,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]; @@ -106,13 +126,20 @@ void MixedBehaviorProfile::RealizationProbs(const MixedStrategyProfile &mp template MixedBehaviorProfile::MixedBehaviorProfile(const MixedStrategyProfile &p_profile) - : m_probs(p_profile.GetGame()->NumActions()), m_support(p_profile.GetGame()), + : m_probs(p_profile.GetGame()->BehavProfileLength()), m_support(p_profile.GetGame()), m_gameversion(p_profile.GetGame()->GetVersion()) { - static_cast &>(m_probs) = T(0); + int index = 1; + for (const auto &player : p_profile.GetGame()->GetPlayers()) { + for (const auto &infoset : player->GetInfosets()) { + for (const auto &action : infoset->GetActions()) { + m_profileIndex[action] = index; + m_probs[index++] = static_cast(0); + } + } + } - 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(); @@ -136,25 +163,24 @@ template MixedBehaviorProfile & MixedBehaviorProfile::operator=(const MixedBehaviorProfile &p_profile) { - if (this != &p_profile && m_support == p_profile.m_support) { - InvalidateCache(); - m_probs = p_profile.m_probs; - m_support = p_profile.m_support; - m_gameversion = p_profile.m_gameversion; + if (this == &p_profile) { + return *this; } + if (m_support != p_profile.m_support) { + throw MismatchException(); + } + InvalidateCache(); + m_probs = p_profile.m_probs; + m_gameversion = p_profile.m_gameversion; + map_realizProbs = p_profile.map_realizProbs; + map_beliefs = p_profile.map_beliefs; + map_nodeValues = p_profile.map_nodeValues; + map_infosetValues = p_profile.map_infosetValues; + map_actionValues = p_profile.map_actionValues; + map_regret = p_profile.map_regret; return *this; } -//======================================================================== -// MixedBehaviorProfile: Operator overloading -//======================================================================== - -template -bool MixedBehaviorProfile::operator==(const MixedBehaviorProfile &p_profile) const -{ - return (m_support == p_profile.m_support && (DVector &)*this == (DVector &)p_profile); -} - //======================================================================== // MixedBehaviorProfile: General data access //======================================================================== @@ -163,8 +189,8 @@ template void MixedBehaviorProfile::SetCentroid() { CheckVersion(); for (auto infoset : m_support.GetGame()->GetInfosets()) { - if (m_support.NumActions(infoset) > 0) { - T center = T(1) / T(m_support.NumActions(infoset)); + if (!m_support.GetActions(infoset).empty()) { + T center = T(1) / T(m_support.GetActions(infoset).size()); for (auto act : m_support.GetActions(infoset)) { (*this)[act] = center; } @@ -175,7 +201,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; @@ -186,7 +212,7 @@ template void MixedBehaviorProfile::UndefinedToCentroid() [this](T total, GameAction act) { return total + GetActionProb(act); }); if (total == T(0)) { for (auto act : actions) { - (*this)[act] = T(1) / T(m_support.NumActions(infoset)); + (*this)[act] = T(1) / T(m_support.GetActions(infoset).size()); } } } @@ -214,6 +240,21 @@ template MixedBehaviorProfile MixedBehaviorProfile::Normalize() return norm; } +template MixedBehaviorProfile MixedBehaviorProfile::ToFullSupport() const +{ + CheckVersion(); + MixedBehaviorProfile full(GetGame()); + + for (auto player : m_support.GetGame()->GetPlayers()) { + for (auto infoset : player->GetInfosets()) { + for (auto action : infoset->GetActions()) { + full[action] = (m_support.Contains(action)) ? (*this)[action] : T(0); + } + } + } + return full; +} + //======================================================================== // MixedBehaviorProfile: Interesting quantities //======================================================================== @@ -246,8 +287,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; } @@ -290,17 +331,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(action->GetInfoset()->GetPlayer()->GetNumber(), - action->GetInfoset()->GetNumber(), m_support.GetIndex(action)); - } + return m_probs[m_profileIndex.at(action)]; } template const T &MixedBehaviorProfile::GetPayoff(const GameAction &act) const @@ -339,7 +375,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()) { @@ -384,16 +420,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()); @@ -409,7 +445,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); } @@ -429,29 +465,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; + } } //======================================================================== @@ -476,9 +508,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); } } @@ -486,7 +518,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), @@ -508,7 +540,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 4c4c11cfd..fabf9fa27 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 { @@ -33,8 +35,10 @@ namespace Gambit { /// template class MixedBehaviorProfile { protected: - DVector m_probs; + Vector m_probs; BehaviorSupportProfile m_support; + /// The index into the action profile for a action (-1 if not in support) + std::map m_profileIndex; unsigned int m_gameversion; // structures for storing cached data: nodes @@ -61,7 +65,7 @@ template class MixedBehaviorProfile { //@{ void BehaviorStrat(GamePlayer &, GameNode &, std::map &, std::map &); void RealizationProbs(const MixedStrategyProfile &, GamePlayer &, const Array &, - GameTreeNodeRep *, std::map &, std::map &); + GameNodeRep *, std::map &, std::map &); //@} /// Check underlying game has not changed; raise exception if it has @@ -99,22 +103,23 @@ template class MixedBehaviorProfile { /// @name Operator overloading //@{ - bool operator==(const MixedBehaviorProfile &) const; - bool operator!=(const MixedBehaviorProfile &x) const { return !(*this == x); } - - bool operator==(const DVector &x) const { return m_probs == x; } - bool operator!=(const DVector &x) const { return m_probs != x; } + bool operator==(const MixedBehaviorProfile &p_profile) const + { + return (m_support == p_profile.m_support && m_probs == p_profile.m_probs); + } + bool operator!=(const MixedBehaviorProfile &p_profile) const + { + return (m_support != p_profile.m_support || m_probs != p_profile.m_probs); + } const T &operator[](const GameAction &p_action) const { - return m_probs(p_action->GetInfoset()->GetPlayer()->GetNumber(), - p_action->GetInfoset()->GetNumber(), m_support.GetIndex(p_action)); + return m_probs[m_profileIndex.at(p_action)]; } T &operator[](const GameAction &p_action) { InvalidateCache(); - return m_probs(p_action->GetInfoset()->GetPlayer()->GetNumber(), - p_action->GetInfoset()->GetNumber(), m_support.GetIndex(p_action)); + return m_probs[m_profileIndex.at(p_action)]; } const T &operator[](int a) const { return m_probs[a]; } @@ -155,7 +160,7 @@ template class MixedBehaviorProfile { /// @name General data access //@{ - size_t BehaviorProfileLength() const { return m_probs.Length(); } + size_t BehaviorProfileLength() const { return m_probs.size(); } Game GetGame() const { return m_support.GetGame(); } const BehaviorSupportProfile &GetSupport() const { return m_support; } /// Returns whether the profile has been invalidated by a subsequent revision to the game @@ -212,6 +217,8 @@ template class MixedBehaviorProfile { MixedStrategyProfile ToMixedProfile() const; + /// @brief Converts the profile to one on the full support of the game + MixedBehaviorProfile ToFullSupport() const; //@} }; @@ -219,7 +226,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()) { @@ -237,7 +244,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 822083cde..e023785e0 100644 --- a/src/games/behavpure.cc +++ b/src/games/behavpure.cc @@ -32,14 +32,10 @@ namespace Gambit { // PureBehaviorProfile: Lifecycle //------------------------------------------------------------------------ -PureBehaviorProfile::PureBehaviorProfile(Game p_efg) : m_efg(p_efg), m_profile(m_efg->NumPlayers()) +PureBehaviorProfile::PureBehaviorProfile(const Game &p_efg) : m_efg(p_efg) { - for (int pl = 1; pl <= m_efg->NumPlayers(); pl++) { - GamePlayerRep *player = m_efg->GetPlayer(pl); - m_profile[pl] = Array(player->NumInfosets()); - for (int iset = 1; iset <= player->NumInfosets(); iset++) { - m_profile[pl][iset] = player->GetInfoset(iset)->GetAction(1); - } + for (const auto &infoset : m_efg->GetInfosets()) { + m_profile[infoset] = infoset->GetActions().front(); } } @@ -47,36 +43,24 @@ PureBehaviorProfile::PureBehaviorProfile(Game p_efg) : m_efg(p_efg), m_profile(m // PureBehaviorProfile: Data access and manipulation //------------------------------------------------------------------------ -GameAction PureBehaviorProfile::GetAction(const GameInfoset &infoset) const -{ - return m_profile[infoset->GetPlayer()->GetNumber()][infoset->GetNumber()]; -} - -void PureBehaviorProfile::SetAction(const GameAction &action) -{ - m_profile[action->GetInfoset()->GetPlayer()->GetNumber()][action->GetInfoset()->GetNumber()] = - action; -} - -template T PureBehaviorProfile::GetPayoff(const GameNode &p_node, int pl) const +template +T PureBehaviorProfile::GetPayoff(const GameNode &p_node, const GamePlayer &p_player) const { T payoff(0); if (p_node->GetOutcome()) { - payoff += static_cast(p_node->GetOutcome()->GetPayoff(pl)); + payoff += p_node->GetOutcome()->GetPayoff(p_player); } if (!p_node->IsTerminal()) { if (p_node->GetInfoset()->IsChanceInfoset()) { - for (int i = 1; i <= p_node->NumChildren(); i++) { - payoff += (static_cast(p_node->GetInfoset()->GetActionProb(i)) * - GetPayoff(p_node->GetChild(i), pl)); + for (const auto &action : p_node->GetInfoset()->GetActions()) { + payoff += (static_cast(p_node->GetInfoset()->GetActionProb(action)) * + GetPayoff(p_node->GetChild(action), p_player)); } } else { - int player = p_node->GetPlayer()->GetNumber(); - int iset = p_node->GetInfoset()->GetNumber(); - payoff += GetPayoff(p_node->GetChild(m_profile[player][iset]->GetNumber()), pl); + payoff += GetPayoff(p_node->GetChild(m_profile.at(p_node->GetInfoset())), p_player); } } @@ -84,143 +68,93 @@ template T PureBehaviorProfile::GetPayoff(const GameNode &p_node, int } // Explicit instantiations -template double PureBehaviorProfile::GetPayoff(const GameNode &, int pl) const; -template Rational PureBehaviorProfile::GetPayoff(const GameNode &, int pl) const; +template double PureBehaviorProfile::GetPayoff(const GameNode &, const GamePlayer &) const; +template Rational PureBehaviorProfile::GetPayoff(const GameNode &, const GamePlayer &) const; template T PureBehaviorProfile::GetPayoff(const GameAction &p_action) const { PureBehaviorProfile copy(*this); copy.SetAction(p_action); - return copy.GetPayoff(p_action->GetInfoset()->GetPlayer()->GetNumber()); + return copy.GetPayoff(p_action->GetInfoset()->GetPlayer()); } // Explicit instantiations template double PureBehaviorProfile::GetPayoff(const GameAction &) const; template Rational PureBehaviorProfile::GetPayoff(const GameAction &) const; -bool PureBehaviorProfile::IsAgentNash() const -{ - for (auto player : m_efg->GetPlayers()) { - auto current = GetPayoff(player); - for (auto infoset : player->GetInfosets()) { - for (auto action : infoset->GetActions()) { - if (GetPayoff(action) > current) { - return false; - } - } - } - } - return true; -} - MixedBehaviorProfile PureBehaviorProfile::ToMixedBehaviorProfile() const { MixedBehaviorProfile temp(m_efg); temp = Rational(0); - for (auto player : m_efg->GetPlayers()) { - for (auto infoset : player->GetInfosets()) { - temp[GetAction(infoset)] = Rational(1); + for (const auto &player : m_efg->GetPlayers()) { + for (const auto &infoset : player->GetInfosets()) { + temp[m_profile.at(infoset)] = Rational(1); } } return temp; } //======================================================================== -// class BehaviorProfileIterator +// class BehaviorContingencies //======================================================================== -BehaviorProfileIterator::BehaviorProfileIterator(const Game &p_game) - : m_atEnd(false), m_support(p_game), m_currentBehav(p_game->NumInfosets()), m_profile(p_game), - m_frozenPlayer(0), m_frozenInfoset(0), m_numActiveInfosets(p_game->NumPlayers()) +BehaviorContingencies::BehaviorContingencies(const BehaviorSupportProfile &p_support, + const std::set &p_reachable, + const std::vector &p_frozen) + : m_support(p_support), m_frozen(p_frozen) { - for (int pl = 1; pl <= p_game->NumPlayers(); pl++) { - GamePlayer player = p_game->GetPlayer(pl); - m_numActiveInfosets[pl] = player->NumInfosets(); - Array activeForPl(player->NumInfosets()); - for (int iset = 1; iset <= player->NumInfosets(); iset++) { - activeForPl[iset] = true; + if (!p_reachable.empty()) { + for (const auto &infoset : p_reachable) { + m_activeInfosets.push_back(infoset); } - m_isActive.push_back(activeForPl); } - First(); -} - -BehaviorProfileIterator::BehaviorProfileIterator(const BehaviorSupportProfile &p_support, - const GameAction &p_action) - : m_atEnd(false), m_support(p_support), m_currentBehav(p_support.GetGame()->NumInfosets()), - m_profile(p_support.GetGame()), - m_frozenPlayer(p_action->GetInfoset()->GetPlayer()->GetNumber()), - m_frozenInfoset(p_action->GetInfoset()->GetNumber()), - m_numActiveInfosets(m_support.GetGame()->NumPlayers()) -{ - for (int pl = 1; pl <= m_support.GetGame()->NumPlayers(); pl++) { - GamePlayer player = m_support.GetGame()->GetPlayer(pl); - m_numActiveInfosets[pl] = 0; - Array activeForPl(player->NumInfosets()); - for (int iset = 1; iset <= player->NumInfosets(); iset++) { - activeForPl[iset] = p_support.IsReachable(player->GetInfoset(iset)); - m_numActiveInfosets[pl]++; - } - m_isActive.push_back(activeForPl); - } - - m_currentBehav(m_frozenPlayer, m_frozenInfoset) = p_support.GetIndex(p_action); - m_profile.SetAction(p_action); - First(); -} - -void BehaviorProfileIterator::First() -{ - for (int pl = 1; pl <= m_support.GetGame()->NumPlayers(); pl++) { - for (int iset = 1; iset <= m_support.GetGame()->GetPlayer(pl)->NumInfosets(); iset++) { - if (pl != m_frozenPlayer && iset != m_frozenInfoset) { - m_currentBehav(pl, iset) = 1; - if (m_isActive[pl][iset]) { - m_profile.SetAction(m_support.GetAction(pl, iset, 1)); + else { + for (const auto &player : m_support.GetGame()->GetPlayers()) { + for (const auto &infoset : player->GetInfosets()) { + if (p_support.IsReachable(infoset)) { + m_activeInfosets.push_back(infoset); } } } } + for (const auto &action : m_frozen) { + m_activeInfosets.erase(std::find_if( + m_activeInfosets.begin(), m_activeInfosets.end(), + [action](const GameInfoset &infoset) { return infoset == action->GetInfoset(); })); + } } -void BehaviorProfileIterator::operator++() +BehaviorContingencies::iterator::iterator(BehaviorContingencies *p_cont, bool p_end) + : m_cont(p_cont), m_atEnd(p_end), m_profile(p_cont->m_support.GetGame()) { - int pl = m_support.GetGame()->NumPlayers(); - while (pl > 0 && m_numActiveInfosets[pl] == 0) { - --pl; - } - if (pl == 0) { - m_atEnd = true; + if (m_atEnd) { return; } - - int iset = m_support.GetGame()->GetPlayer(pl)->NumInfosets(); - - while (true) { - if (m_isActive[pl][iset] && (pl != m_frozenPlayer || iset != m_frozenInfoset)) { - if (m_currentBehav(pl, iset) < m_support.NumActions(pl, iset)) { - m_profile.SetAction(m_support.GetAction(pl, iset, ++m_currentBehav(pl, iset))); - return; - } - else { - m_currentBehav(pl, iset) = 1; - m_profile.SetAction(m_support.GetAction(pl, iset, 1)); - } + for (const auto &player : m_cont->m_support.GetGame()->GetPlayers()) { + for (const auto &infoset : player->GetInfosets()) { + m_currentBehav[infoset] = m_cont->m_support.GetActions(infoset).begin(); + m_profile.SetAction(*m_currentBehav[infoset]); } + } + for (const auto &action : m_cont->m_frozen) { + m_profile.SetAction(action); + } +} - iset--; - if (iset == 0) { - do { - --pl; - } while (pl > 0 && m_numActiveInfosets[pl] == 0); - - if (pl == 0) { - m_atEnd = true; - return; - } - iset = m_support.GetGame()->GetPlayer(pl)->NumInfosets(); +BehaviorContingencies::iterator &BehaviorContingencies::iterator::operator++() +{ + for (auto infoset = m_cont->m_activeInfosets.crbegin(); + infoset != m_cont->m_activeInfosets.crend(); ++infoset) { + ++m_currentBehav[*infoset]; + if (m_currentBehav.at(*infoset) != m_cont->m_support.GetActions(*infoset).end()) { + m_profile.SetAction(*m_currentBehav[*infoset]); + return *this; } + m_currentBehav[*infoset] = m_cont->m_support.GetActions(*infoset).begin(); + m_profile.SetAction(*m_currentBehav[*infoset]); } + m_atEnd = true; + return *this; } } // end namespace Gambit diff --git a/src/games/behavpure.h b/src/games/behavpure.h index f5c22d141..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 { @@ -33,96 +34,106 @@ namespace Gambit { class PureBehaviorProfile { private: Game m_efg; - Array> m_profile; + std::map m_profile; public: /// @name Lifecycle //@{ /// Construct a new behavior profile on the specified game - explicit PureBehaviorProfile(Game); + explicit PureBehaviorProfile(const Game &); + //@} + + Game GetGame() const { return m_efg; } + + bool operator==(const PureBehaviorProfile &p_other) const + { + return m_profile == p_other.m_profile; + } /// @name Data access and manipulation //@{ /// Get the action played at an information set - GameAction GetAction(const GameInfoset &) const; + GameAction GetAction(const GameInfoset &p_infoset) const { return m_profile.at(p_infoset); } /// Set the action played at an information set - void SetAction(const GameAction &); + void SetAction(const GameAction &p_action) { m_profile[p_action->GetInfoset()] = p_action; } - /// Get the payoff to player pl that results from the profile - template T GetPayoff(int pl) const; /// Get the payoff to the player that results from the profile - template T GetPayoff(const GamePlayer &p_player) const - { - return GetPayoff(p_player->GetNumber()); - } - /// Get the payoff to player pl conditional on reaching a node - template T GetPayoff(const GameNode &, int pl) const; + template T GetPayoff(const GamePlayer &p_player) const; + /// Get the payoff to the player, conditional on reaching a node + template T GetPayoff(const GameNode &, const GamePlayer &) const; /// 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; //@} }; -template <> inline double PureBehaviorProfile::GetPayoff(int pl) const +template <> inline double PureBehaviorProfile::GetPayoff(const GamePlayer &p_player) const { - return GetPayoff(m_efg->GetRoot(), pl); + return GetPayoff(m_efg->GetRoot(), p_player); } -template <> inline Rational PureBehaviorProfile::GetPayoff(int pl) const +template <> inline Rational PureBehaviorProfile::GetPayoff(const GamePlayer &p_player) const { - return GetPayoff(m_efg->GetRoot(), pl); + return GetPayoff(m_efg->GetRoot(), p_player); } -template <> inline std::string PureBehaviorProfile::GetPayoff(int pl) const +template <> inline std::string PureBehaviorProfile::GetPayoff(const GamePlayer &p_player) const { - return lexical_cast(GetPayoff(m_efg->GetRoot(), pl)); + return lexical_cast(GetPayoff(m_efg->GetRoot(), p_player)); } -// -// Currently, the contingency iterator only allows one information -// set to be "frozen". There is a list of "active" information -// sets, which are those whose actions are cycled over, the idea -// being that behavior at inactive information sets is irrelevant. -// -class BehaviorProfileIterator { +class BehaviorContingencies { private: - bool m_atEnd; BehaviorSupportProfile m_support; - PVector m_currentBehav; - PureBehaviorProfile m_profile; - int m_frozenPlayer, m_frozenInfoset; - Array> m_isActive; - Array m_numActiveInfosets; - - /// Reset the iterator to the first contingency (this is called by ctors) - void First(); + std::vector m_frozen; + std::list m_activeInfosets; public: + class iterator { + private: + BehaviorContingencies *m_cont; + bool m_atEnd; + std::map m_currentBehav; + PureBehaviorProfile m_profile; + + public: + using iterator_category = std::forward_iterator_tag; + using difference_type = std::ptrdiff_t; + using value_type = PureBehaviorProfile; + using pointer = value_type *; + using reference = value_type &; + + iterator(BehaviorContingencies *, bool p_end); + + iterator &operator++(); + + bool operator==(const iterator &p_other) const + { + if (m_atEnd && p_other.m_atEnd && m_cont == p_other.m_cont) { + return true; + } + if (m_atEnd != p_other.m_atEnd || m_cont != p_other.m_cont) { + return false; + } + return (m_profile == p_other.m_profile); + } + bool operator!=(const iterator &p_other) const { return !(*this == p_other); } + + PureBehaviorProfile &operator*() { return m_profile; } + const PureBehaviorProfile &operator*() const { return m_profile; } + }; /// @name Lifecycle //@{ - /// Construct a new iterator on the game, with no actions held fixed - explicit BehaviorProfileIterator(const Game &); - /// Construct a new iterator on the support, holding the action fixed - BehaviorProfileIterator(const BehaviorSupportProfile &, const GameAction &); - //@} - - /// @name Iteration and data access - //@{ - /// Advance to the next contingency (prefix version) - void operator++(); - /// Advance to the next contingency (postfix version) - void operator++(int) { ++(*this); } - /// Has iterator gone past the end? - bool AtEnd() const { return m_atEnd; } - /// Get the current behavior profile - const PureBehaviorProfile &operator*() const { return m_profile; } + /// Construct a new iterator on the support, holding the listed actions fixed + explicit BehaviorContingencies(const BehaviorSupportProfile &, + const std::set &p_active = {}, + const std::vector &p_frozen = {}); //@} + iterator begin() { return {this, false}; } + iterator end() { return {this, true}; } }; } // end namespace Gambit diff --git a/src/games/behavspt.cc b/src/games/behavspt.cc index 070d81754..71eec0931 100644 --- a/src/games/behavspt.cc +++ b/src/games/behavspt.cc @@ -30,140 +30,66 @@ namespace Gambit { BehaviorSupportProfile::BehaviorSupportProfile(const Game &p_efg) : m_efg(p_efg) { - for (int pl = 1; pl <= p_efg->NumPlayers(); pl++) { - m_actions.push_back(Array>()); - GamePlayer player = p_efg->GetPlayer(pl); - for (int iset = 1; iset <= player->NumInfosets(); iset++) { - GameInfoset infoset = player->GetInfoset(iset); - m_actions[pl].push_back(Array()); - for (int act = 1; act <= infoset->NumActions(); act++) { - m_actions[pl][iset].push_back(infoset->GetAction(act)); + for (const auto &player : p_efg->GetPlayers()) { + for (const auto &infoset : player->GetInfosets()) { + for (const auto &action : infoset->GetActions()) { + m_actions[infoset].push_back(action); } } } // Initialize the list of reachable information sets and nodes - for (int pl = 0; pl <= GetGame()->NumPlayers(); pl++) { - GamePlayer player = (pl == 0) ? GetGame()->GetChance() : GetGame()->GetPlayer(pl); - for (int iset = 1; iset <= player->NumInfosets(); iset++) { - m_infosetReachable[player->GetInfoset(iset)] = false; - for (int n = 1; n <= player->GetInfoset(iset)->NumMembers(); n++) { - m_nonterminalReachable[player->GetInfoset(iset)->GetMember(n)] = false; + 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; + for (const auto &member : infoset->GetMembers()) { + m_nonterminalReachable[member] = true; } } } - ActivateSubtree(GetGame()->GetRoot()); -} - -//======================================================================== -// BehaviorSupportProfile: Operator overloading -//======================================================================== - -bool BehaviorSupportProfile::operator==(const BehaviorSupportProfile &p_support) const -{ - return (m_actions == p_support.m_actions); } //======================================================================== // BehaviorSupportProfile: General information //======================================================================== -PVector BehaviorSupportProfile::NumActions() const -{ - PVector answer(m_efg->NumInfosets()); - for (int pl = 1; pl <= m_efg->NumPlayers(); pl++) { - for (int iset = 1; iset <= m_efg->GetPlayer(pl)->NumInfosets(); iset++) { - answer(pl, iset) = NumActions(pl, iset); - } - } - - return answer; -} - size_t BehaviorSupportProfile::BehaviorProfileLength() const { size_t answer = 0; - for (const auto &player : m_actions) { - for (const auto &infoset : player) { - answer += infoset.size(); - } + for (const auto &[infoset, actions] : m_actions) { + answer += actions.size(); } return answer; } -int BehaviorSupportProfile::GetIndex(const GameAction &a) const -{ - if (a->GetInfoset()->GetGame() != m_efg) { - throw MismatchException(); - } - - int pl = a->GetInfoset()->GetPlayer()->GetNumber(); - if (pl == 0) { - // chance action; all chance actions are always in the support - return a->GetNumber(); - } - else { - return m_actions[pl][a->GetInfoset()->GetNumber()].Find(a); - } -} - -bool BehaviorSupportProfile::RemoveAction(const GameAction &s) -{ - for (const auto &node : GetMembers(s->GetInfoset())) { - DeactivateSubtree(node->GetChild(s)); - } - - GameInfoset infoset = s->GetInfoset(); - GamePlayer player = infoset->GetPlayer(); - Array &actions = m_actions[player->GetNumber()][infoset->GetNumber()]; - - if (!actions.Contains(s)) { - return false; - } - else if (actions.Length() == 1) { - actions.Remove(actions.Find(s)); - return false; - } - else { - actions.Remove(actions.Find(s)); - return true; - } -} - -bool BehaviorSupportProfile::RemoveAction(const GameAction &s, List &list) +void BehaviorSupportProfile::AddAction(const GameAction &p_action) { - for (const auto &node : GetMembers(s->GetInfoset())) { - DeactivateSubtree(node->GetChild(s->GetNumber()), list); + auto &support = m_actions.at(p_action->GetInfoset()); + auto pos = std::find_if(support.begin(), support.end(), [p_action](const GameAction &a) { + return a->GetNumber() >= p_action->GetNumber(); + }); + if (pos == support.end() || *pos != p_action) { + // Action is not in the support at the infoset; add at this location to keep sorted by number + support.insert(pos, p_action); + for (const auto &node : GetMembers(p_action->GetInfoset())) { + ActivateSubtree(node->GetChild(p_action)); + } } - // the following returns false if s was not in the support - return RemoveAction(s); } -void BehaviorSupportProfile::AddAction(const GameAction &s) +bool BehaviorSupportProfile::RemoveAction(const GameAction &p_action) { - GameInfoset infoset = s->GetInfoset(); - GamePlayer player = infoset->GetPlayer(); - Array &actions = m_actions[player->GetNumber()][infoset->GetNumber()]; - - int act = 1; - while (act <= actions.Length()) { - if (actions[act] == s) { - break; - } - else if (actions[act]->GetNumber() > s->GetNumber()) { - actions.Insert(s, act); - break; + auto &support = m_actions.at(p_action->GetInfoset()); + auto pos = std::find(support.begin(), support.end(), p_action); + if (pos != support.end()) { + support.erase(pos); + for (const auto &node : GetMembers(p_action->GetInfoset())) { + DeactivateSubtree(node->GetChild(p_action)); } - act++; - } - - if (act > actions.Length()) { - actions.push_back(s); - } - - for (const auto &node : GetMembers(s->GetInfoset())) { - DeactivateSubtree(node); + return !support.empty(); } + return false; } std::list BehaviorSupportProfile::GetInfosets(const GamePlayer &p_player) const @@ -178,17 +104,17 @@ std::list BehaviorSupportProfile::GetInfosets(const GamePlayer &p_p } namespace { -/// Sets p_reachable(pl,iset) to 1 if infoset (pl,iset) reachable after p_node + void ReachableInfosets(const BehaviorSupportProfile &p_support, const GameNode &p_node, - PVector &p_reached) + std::set &p_reached) { if (p_node->IsTerminal()) { return; } - GameInfoset infoset = p_node->GetInfoset(); + const GameInfoset infoset = p_node->GetInfoset(); if (!infoset->GetPlayer()->IsChance()) { - p_reached(infoset->GetPlayer()->GetNumber(), infoset->GetNumber()) = 1; + p_reached.insert(infoset); for (const auto &action : p_support.GetActions(infoset)) { ReachableInfosets(p_support, p_node->GetChild(action), p_reached); } @@ -202,307 +128,86 @@ void ReachableInfosets(const BehaviorSupportProfile &p_support, const GameNode & } // end anonymous namespace -// This class iterates -// over contingencies that are relevant once a particular node -// has been reached. -class BehavConditionalIterator { -private: - bool m_atEnd{false}; - BehaviorSupportProfile m_support; - PVector m_currentBehav; - PureBehaviorProfile m_profile; - PVector m_isActive; - Array m_numActiveInfosets; - - /// Reset the iterator to the first contingency (this is called by ctors) - void First(); - -public: - /// @name Lifecycle - //@{ - BehavConditionalIterator(const BehaviorSupportProfile &, const PVector &); - //@} - - /// @name Iteration and data access - //@{ - /// Advance to the next contingency (prefix version) - void operator++(); - /// Advance to the next contingency (postfix version) - void operator++(int) { ++(*this); } - /// Has iterator gone past the end? - bool AtEnd() const { return m_atEnd; } - /// Get the current behavior profile - const PureBehaviorProfile *operator->() const { return &m_profile; } - //@} -}; - -BehavConditionalIterator::BehavConditionalIterator(const BehaviorSupportProfile &p_support, - const PVector &p_active) - : m_support(p_support), m_currentBehav(m_support.GetGame()->NumInfosets()), - m_profile(m_support.GetGame()), m_isActive(p_active), - m_numActiveInfosets(m_support.GetGame()->NumPlayers()) -{ - for (int pl = 1; pl <= m_support.GetGame()->NumPlayers(); pl++) { - m_numActiveInfosets[pl] = 0; - GamePlayer player = m_support.GetGame()->GetPlayer(pl); - for (int iset = 1; iset <= player->NumInfosets(); iset++) { - if (m_isActive(pl, iset)) { - m_numActiveInfosets[pl]++; - } - } - } - First(); -} - -void BehavConditionalIterator::First() -{ - for (int pl = 1; pl <= m_support.GetGame()->NumPlayers(); pl++) { - for (int iset = 1; iset <= m_support.GetGame()->GetPlayer(pl)->NumInfosets(); iset++) { - m_currentBehav(pl, iset) = 1; - if (m_isActive(pl, iset)) { - m_profile.SetAction(m_support.GetAction(pl, iset, 1)); - } - } - } -} - -void BehavConditionalIterator::operator++() +bool BehaviorSupportProfile::Dominates(const GameAction &a, const GameAction &b, + bool p_strict) const { - int pl = m_support.GetGame()->NumPlayers(); - while (pl > 0 && m_numActiveInfosets[pl] == 0) { - --pl; - } - if (pl == 0) { - m_atEnd = true; - return; - } - - int iset = m_support.GetGame()->GetPlayer(pl)->NumInfosets(); - - while (true) { - if (m_isActive(pl, iset)) { - if (m_currentBehav(pl, iset) < m_support.NumActions(pl, iset)) { - m_profile.SetAction(m_support.GetAction(pl, iset, ++m_currentBehav(pl, iset))); - return; - } - else { - m_currentBehav(pl, iset) = 1; - m_profile.SetAction(m_support.GetAction(pl, iset, 1)); - } - } - - iset--; - if (iset == 0) { - do { - --pl; - } while (pl > 0 && m_numActiveInfosets[pl] == 0); - - if (pl == 0) { - m_atEnd = true; - return; - } - iset = m_support.GetGame()->GetPlayer(pl)->NumInfosets(); - } - } -} - -bool BehaviorSupportProfile::Dominates(const GameAction &a, const GameAction &b, bool p_strict, - bool p_conditional) const -{ - GameInfoset infoset = a->GetInfoset(); + const GameInfoset infoset = a->GetInfoset(); if (infoset != b->GetInfoset()) { throw UndefinedException(); } GamePlayer player = infoset->GetPlayer(); - int pl = player->GetNumber(); - bool equal = true; - - if (!p_conditional) { - for (BehaviorProfileIterator iter(*this, a); !iter.AtEnd(); iter++) { - auto ap = (*iter).GetPayoff(a); - auto bp = (*iter).GetPayoff(b); - - if (p_strict) { - if (ap <= bp) { - return false; - } - } - else { - if (ap < bp) { - return false; - } - else if (ap > bp) { - equal = false; - } - } - } - } - - else { - auto nodelist = GetMembers(infoset); - if (nodelist.empty()) { - // This may not be a good idea; I suggest checking for this - // prior to entry - for (const auto &member : infoset->GetMembers()) { - nodelist.push_back(member); + int thesign = 0; + + auto nodelist = GetMembers(infoset); + for (const auto &node : GetMembers(infoset)) { + std::set reachable; + ReachableInfosets(*this, node->GetChild(a), reachable); + ReachableInfosets(*this, node->GetChild(b), reachable); + + auto contingencies = BehaviorContingencies(*this, reachable); + if (p_strict) { + if (!std::all_of(contingencies.begin(), contingencies.end(), + [&](const PureBehaviorProfile &profile) { + return profile.GetPayoff(node->GetChild(a), player) > + profile.GetPayoff(node->GetChild(b), player); + })) { + return false; } } - - for (const auto &node : nodelist) { - PVector reachable(GetGame()->NumInfosets()); - reachable = 0; - ReachableInfosets(*this, node->GetChild(a), reachable); - ReachableInfosets(*this, node->GetChild(b), reachable); - - for (BehavConditionalIterator iter(*this, reachable); !iter.AtEnd(); iter++) { - auto ap = iter->GetPayoff(node->GetChild(a), pl); - auto bp = iter->GetPayoff(node->GetChild(b), pl); - - if (p_strict) { - if (ap <= bp) { - return false; - } - } - else { - if (ap < bp) { - return false; - } - else if (ap > bp) { - equal = false; - } + else { + for (const auto &iter : contingencies) { + auto newsign = sign(iter.GetPayoff(node->GetChild(a), player) - + iter.GetPayoff(node->GetChild(b), player)); + if (newsign < 0) { + return false; } + thesign = std::max(thesign, newsign); } } } - - if (p_strict) { - return true; - } - else { - return !equal; - } -} - -bool SomeElementDominates(const BehaviorSupportProfile &S, const Array &array, - const GameAction &a, const bool strong, const bool conditional) -{ - for (int i = 1; i <= array.Length(); i++) { - if (array[i] != a) { - if (S.Dominates(array[i], a, strong, conditional)) { - return true; - } - } - } - return false; + return p_strict || thesign > 0; } -bool BehaviorSupportProfile::IsDominated(const GameAction &a, bool strong, bool conditional) const +bool BehaviorSupportProfile::IsDominated(const GameAction &p_action, const bool p_strict) const { - int pl = a->GetInfoset()->GetPlayer()->GetNumber(); - int iset = a->GetInfoset()->GetNumber(); - Array array(m_actions[pl][iset]); - return SomeElementDominates(*this, array, a, strong, conditional); -} - -bool InfosetHasDominatedElement(const BehaviorSupportProfile &S, const GameInfoset &p_infoset, - bool strong, bool conditional) -{ - int pl = p_infoset->GetPlayer()->GetNumber(); - int iset = p_infoset->GetNumber(); - Array actions; - for (int act = 1; act <= S.NumActions(pl, iset); act++) { - actions.push_back(S.GetAction(pl, iset, act)); - } - for (int i = 1; i <= actions.Length(); i++) { - if (SomeElementDominates(S, actions, actions[i], strong, conditional)) { - return true; - } - } - - return false; + const auto &actions = GetActions(p_action->GetInfoset()); + return std::any_of(actions.begin(), actions.end(), [&](const GameAction &a) { + return a != p_action && Dominates(a, p_action, p_strict); + }); } -bool ElimDominatedInInfoset(const BehaviorSupportProfile &S, BehaviorSupportProfile &T, int pl, - int iset, bool strong, bool conditional) +BehaviorSupportProfile BehaviorSupportProfile::Undominated(const bool p_strict) const { - Array actions; - for (int act = 1; act <= S.NumActions(pl, iset); act++) { - actions.push_back(S.GetAction(pl, iset, act)); - } - Array is_dominated(actions.Length()); - for (int k = 1; k <= actions.Length(); k++) { - is_dominated[k] = false; - } - - for (int i = 1; i <= actions.Length(); i++) { - for (int j = 1; j <= actions.Length(); j++) { - if (i != j && !is_dominated[i] && !is_dominated[j]) { - if (S.Dominates(actions[i], actions[j], strong, conditional)) { - is_dominated[j] = true; + BehaviorSupportProfile result(*this); + for (const auto &player : m_efg->GetPlayers()) { + for (const auto &infoset : player->GetInfosets()) { + const auto &actions = GetActions(infoset); + std::set dominated; + for (const auto &action1 : actions) { + if (contains(dominated, action1)) { + continue; + } + for (const auto &action2 : actions) { + if (action1 == action2 || contains(dominated, action2)) { + continue; + } + if (Dominates(action1, action2, p_strict)) { + dominated.insert(action2); + result.RemoveAction(action2); + } } } } } - - bool action_was_eliminated = false; - int k = 1; - while (k <= actions.Length() && !action_was_eliminated) { - if (is_dominated[k]) { - action_was_eliminated = true; - } - else { - k++; - } - } - while (k <= actions.Length()) { - if (is_dominated[k]) { - T.RemoveAction(actions[k]); - } - k++; - } - - return action_was_eliminated; + return result; } -bool ElimDominatedForPlayer(const BehaviorSupportProfile &S, BehaviorSupportProfile &T, - const int pl, int &cumiset, const bool strong, const bool conditional) +bool BehaviorSupportProfile::HasReachableMembers(const GameInfoset &p_infoset) const { - bool action_was_eliminated = false; - - for (int iset = 1; iset <= S.GetGame()->GetPlayer(pl)->NumInfosets(); iset++, cumiset++) { - if (ElimDominatedInInfoset(S, T, pl, iset, strong, conditional)) { - action_was_eliminated = true; - } - } - - return action_was_eliminated; -} - -BehaviorSupportProfile BehaviorSupportProfile::Undominated(bool strong, bool conditional, - const Array &players, - std::ostream &) const -{ - BehaviorSupportProfile T(*this); - int cumiset = 0; - - for (int i = 1; i <= players.Length(); i++) { - int pl = players[i]; - ElimDominatedForPlayer(*this, T, pl, cumiset, strong, conditional); - } - - return T; -} - -// Utilities -bool BehaviorSupportProfile::HasActiveMembers(int pl, int iset) const -{ - for (auto member : m_efg->GetPlayer(pl)->GetInfoset(iset)->GetMembers()) { - if (m_nonterminalReachable.at(member)) { - return true; - } - } - return false; + const auto &members = p_infoset->GetMembers(); + return std::any_of(members.begin(), members.end(), + [&](const GameNode &n) { return m_nonterminalReachable.at(n); }); } void BehaviorSupportProfile::ActivateSubtree(const GameNode &n) @@ -511,15 +216,13 @@ void BehaviorSupportProfile::ActivateSubtree(const GameNode &n) m_nonterminalReachable[n] = true; m_infosetReachable[n->GetInfoset()] = true; if (n->GetInfoset()->GetPlayer()->IsChance()) { - for (int i = 1; i <= n->NumChildren(); i++) { - ActivateSubtree(n->GetChild(i)); + for (const auto &child : n->GetChildren()) { + ActivateSubtree(child); } } else { - const Array &actions( - m_actions[n->GetInfoset()->GetPlayer()->GetNumber()][n->GetInfoset()->GetNumber()]); - for (int i = 1; i <= actions.Length(); i++) { - ActivateSubtree(n->GetChild(actions[i]->GetNumber())); + for (const auto &action : GetActions(n->GetInfoset())) { + ActivateSubtree(n->GetChild(action)); } } } @@ -529,42 +232,22 @@ void BehaviorSupportProfile::DeactivateSubtree(const GameNode &n) { if (!n->IsTerminal()) { // THIS ALL LOOKS FISHY m_nonterminalReachable[n] = false; - if (!HasActiveMembers(n->GetInfoset()->GetPlayer()->GetNumber(), - n->GetInfoset()->GetNumber())) { + if (!HasReachableMembers(n->GetInfoset())) { m_infosetReachable[n->GetInfoset()] = false; } if (!n->GetPlayer()->IsChance()) { - Array actions( - m_actions[n->GetInfoset()->GetPlayer()->GetNumber()][n->GetInfoset()->GetNumber()]); - for (int i = 1; i <= actions.Length(); i++) { - DeactivateSubtree(n->GetChild(actions[i]->GetNumber())); + for (const auto &action : GetActions(n->GetInfoset())) { + DeactivateSubtree(n->GetChild(action)); } } else { - for (int i = 1; i <= n->GetInfoset()->NumActions(); i++) { - DeactivateSubtree(n->GetChild(i)); + for (const auto &action : n->GetInfoset()->GetActions()) { + DeactivateSubtree(n->GetChild(action)); } } } } -void BehaviorSupportProfile::DeactivateSubtree(const GameNode &n, List &list) -{ - if (!n->IsTerminal()) { - m_nonterminalReachable[n] = false; - if (!HasActiveMembers(n->GetInfoset()->GetPlayer()->GetNumber(), - n->GetInfoset()->GetNumber())) { - list.push_back(n->GetInfoset()); - m_infosetReachable[n->GetInfoset()] = false; - } - Array actions( - m_actions[n->GetInfoset()->GetPlayer()->GetNumber()][n->GetInfoset()->GetNumber()]); - for (int i = 1; i <= actions.Length(); i++) { - DeactivateSubtree(n->GetChild(actions[i]->GetNumber()), list); - } - } -} - std::list BehaviorSupportProfile::GetMembers(const GameInfoset &p_infoset) const { std::list answer; diff --git a/src/games/behavspt.h b/src/games/behavspt.h index 95439c98f..bf15cccb5 100644 --- a/src/games/behavspt.h +++ b/src/games/behavspt.h @@ -39,17 +39,39 @@ namespace Gambit { class BehaviorSupportProfile { protected: Game m_efg; - Array>> m_actions; + std::map> m_actions; std::map m_infosetReachable; std::map m_nonterminalReachable; - bool HasActiveMembers(int pl, int iset) const; + bool HasReachableMembers(const GameInfoset &) const; void ActivateSubtree(const GameNode &); void DeactivateSubtree(const GameNode &); - void DeactivateSubtree(const GameNode &, List &); public: + class Support { + private: + const BehaviorSupportProfile *m_profile; + GameInfoset m_infoset; + + public: + using const_iterator = std::vector::const_iterator; + + Support() : m_profile(nullptr), m_infoset(nullptr) {} + Support(const BehaviorSupportProfile *p_profile, const GameInfoset &p_infoset) + : m_profile(p_profile), m_infoset(p_infoset) + { + } + + size_t size() const { return m_profile->m_actions.at(m_infoset).size(); } + bool empty() const { return m_profile->m_actions.at(m_infoset).empty(); } + GameAction front() const { return m_profile->m_actions.at(m_infoset).front(); } + GameAction back() const { return m_profile->m_actions.at(m_infoset).back(); } + + const_iterator begin() const { return m_profile->m_actions.at(m_infoset).begin(); } + const_iterator end() const { return m_profile->m_actions.at(m_infoset).end(); } + }; + /// @name Lifecycle //@{ /// Constructor. By default, a support contains all strategies. @@ -61,44 +83,33 @@ class BehaviorSupportProfile { /// @name Operator overloading //@{ /// Test for the equality of two supports (same actions at all infosets) - bool operator==(const BehaviorSupportProfile &) const; - bool operator!=(const BehaviorSupportProfile &p_support) const { return !(*this == p_support); } + bool operator==(const BehaviorSupportProfile &p_support) const + { + return m_actions == p_support.m_actions; + } + bool operator!=(const BehaviorSupportProfile &p_support) const + { + return m_actions != p_support.m_actions; + } /// @name General information //@{ /// Returns the game on which the support is defined. Game GetGame() const { return m_efg; } - /// Returns the number of actions in the information set - int NumActions(const GameInfoset &p_infoset) const - { - return m_actions[p_infoset->GetPlayer()->GetNumber()][p_infoset->GetNumber()].Length(); - } - int NumActions(int pl, int iset) const { return m_actions[pl][iset].Length(); } - - /// Returns the number of actions in the support for all information sets - PVector NumActions() const; /// Returns the total number of actions in the support size_t BehaviorProfileLength() const; - /// Returns the action at the specified position in the support - GameAction GetAction(int pl, int iset, int act) const { return m_actions[pl][iset][act]; } /// Returns the set of actions in the support at the information set - const Array &GetActions(const GameInfoset &p_infoset) const - { - return m_actions[p_infoset->GetPlayer()->GetNumber()][p_infoset->GetNumber()]; - } + Support GetActions(const GameInfoset &p_infoset) const { return {this, p_infoset}; } /// Does the information set have at least one active action? - bool HasAction(const GameInfoset &p_infoset) const - { - return !m_actions[p_infoset->GetPlayer()->GetNumber()][p_infoset->GetNumber()].empty(); - } - - /// Returns the position of the action in the support. - int GetIndex(const GameAction &) const; + bool HasAction(const GameInfoset &p_infoset) const { return !m_actions.at(p_infoset).empty(); } /// Returns whether the action is in the support. - bool Contains(const GameAction &p_action) const { return (GetIndex(p_action) != 0); } + bool Contains(const GameAction &p_action) const + { + return contains(m_actions.at(p_action->GetInfoset()), p_action); + } //@} /// @name Editing the support @@ -107,9 +118,6 @@ class BehaviorSupportProfile { void AddAction(const GameAction &); /// Removes the action from the support; returns true if successful. bool RemoveAction(const GameAction &); - /// Removes the action and returns the list of information sets - /// made unreachable by the action's removal - bool RemoveAction(const GameAction &, List &); //@} /// @name Reachability of nodes and information sets @@ -124,14 +132,12 @@ class BehaviorSupportProfile { /// @name Identification of dominated actions //@{ - /// Returns true if action a is dominated by action b - bool Dominates(const GameAction &a, const GameAction &b, bool p_strict, - bool p_conditional) const; + /// Returns true if action 'a' is dominated by action 'b' + bool Dominates(const GameAction &a, const GameAction &b, bool p_strict) const; /// Returns true if the action is dominated by some other action - bool IsDominated(const GameAction &a, bool p_strict, bool p_conditional) const; + bool IsDominated(const GameAction &a, bool p_strict) const; /// Returns a copy of the support with dominated actions eliminated - BehaviorSupportProfile Undominated(bool p_strict, bool p_conditional, const Array &players, - std::ostream &) const; + BehaviorSupportProfile Undominated(bool p_strict) const; //@} }; diff --git a/src/games/file.cc b/src/games/file.cc index 01eb8a852..9e5d83680 100644 --- a/src/games/file.cc +++ b/src/games/file.cc @@ -25,6 +25,7 @@ #include #include #include +#include #include "gambit.h" // for explicit access to turning off canonicalization @@ -46,12 +47,8 @@ using GameFileToken = enum { TOKEN_NONE = 7 }; -//! -//! This parser class implements the semantics of Gambit savefiles, -//! including the nonsignificance of whitespace and the possibility of -//! escaped-quotes within text labels. -//! -class GameParserState { +/// @brief Class implementing conversion of classic Gambit savefiles into lexical tokens. +class GameFileLexer { private: std::istream &m_file; @@ -65,23 +62,47 @@ class GameParserState { void IncreaseLine(); public: - explicit GameParserState(std::istream &p_file) : m_file(p_file) {} + explicit GameFileLexer(std::istream &p_file) : m_file(p_file) {} GameFileToken GetNextToken(); GameFileToken GetCurrentToken() const { return m_lastToken; } - int GetCurrentLine() const { return m_currentLine; } - int GetCurrentColumn() const { return m_currentColumn; } - std::string CreateLineMsg(const std::string &msg) const; + /// Throw an InvalidFileException with the specified message + void OnParseError(const std::string &p_message) const; const std::string &GetLastText() const { return m_lastText; } + + void ExpectCurrentToken(GameFileToken p_tokenType, const std::string &p_message) + { + if (GetCurrentToken() != p_tokenType) { + OnParseError("Expected " + p_message); + } + } + void ExpectNextToken(GameFileToken p_tokenType, const std::string &p_message) + { + if (GetNextToken() != p_tokenType) { + OnParseError("Expected " + p_message); + } + } + void AcceptNextToken(GameFileToken p_tokenType) + { + if (GetNextToken() == p_tokenType) { + GetNextToken(); + } + } }; -void GameParserState::ReadChar(char &c) +void GameFileLexer::OnParseError(const std::string &p_message) const +{ + throw InvalidFileException("line " + std::to_string(m_currentLine) + ":" + + std::to_string(m_currentColumn) + ": " + p_message); +} + +void GameFileLexer::ReadChar(char &c) { m_file.get(c); m_currentColumn++; } -void GameParserState::UnreadChar() +void GameFileLexer::UnreadChar() { if (!m_file.eof()) { m_file.unget(); @@ -89,13 +110,13 @@ void GameParserState::UnreadChar() } } -void GameParserState::IncreaseLine() +void GameFileLexer::IncreaseLine() { m_currentLine++; m_currentColumn = 1; } -GameFileToken GameParserState::GetNextToken() +GameFileToken GameFileLexer::GetNextToken() { char c = ' '; if (m_file.eof()) { @@ -148,7 +169,7 @@ GameFileToken GameParserState::GetNextToken() buf += c; ReadChar(c); if (c != '+' && c != '-' && !isdigit(c)) { - throw InvalidFileException(CreateLineMsg("Invalid Token +/-")); + OnParseError("Invalid Token +/-"); } buf += c; ReadChar(c); @@ -177,7 +198,7 @@ GameFileToken GameParserState::GetNextToken() buf += c; ReadChar(c); if (c != '+' && c != '-' && !isdigit(c)) { - throw InvalidFileException(CreateLineMsg("Invalid Token +/-")); + OnParseError("Invalid Token +/-"); } buf += c; ReadChar(c); @@ -230,8 +251,7 @@ GameFileToken GameParserState::GetNextToken() ReadChar(a); while (a != '\"' || lastslash) { if (m_file.eof() || !m_file.good()) { - throw InvalidFileException( - CreateLineMsg("End of file encountered when reading string label")); + OnParseError("End of file encountered when reading string label"); } if (lastslash && a == '"') { m_lastText += '"'; @@ -253,8 +273,7 @@ GameFileToken GameParserState::GetNextToken() m_lastText += a; ReadChar(a); if (m_file.eof()) { - throw InvalidFileException( - CreateLineMsg("End of file encountered when reading string label")); + OnParseError("End of file encountered when reading string label"); } if (a == '\n') { IncreaseLine(); @@ -273,219 +292,88 @@ GameFileToken GameParserState::GetNextToken() return (m_lastToken = TOKEN_SYMBOL); } -std::string GameParserState::CreateLineMsg(const std::string &msg) const -{ - std::stringstream stream; - stream << "line " << m_currentLine << ":" << m_currentColumn << ": " << msg; - return stream.str(); -} - class TableFilePlayer { public: std::string m_name; Array m_strategies; - TableFilePlayer *m_next{nullptr}; - TableFilePlayer() = default; + TableFilePlayer(const std::string &p_name) : m_name(p_name) {} }; class TableFileGame { public: std::string m_title, m_comment; - TableFilePlayer *m_firstPlayer{nullptr}, *m_lastPlayer{nullptr}; - int m_numPlayers{0}; - - TableFileGame() = default; - ~TableFileGame(); + std::vector m_players; - void AddPlayer(const std::string &); - int NumPlayers() const { return m_numPlayers; } - int NumStrategies(int p_player) const; - std::string GetPlayer(int p_player) const; - std::string GetStrategy(int p_player, int p_strategy) const; -}; - -TableFileGame::~TableFileGame() -{ - if (m_firstPlayer) { - TableFilePlayer *player = m_firstPlayer; - while (player) { - TableFilePlayer *nextPlayer = player->m_next; - delete player; - player = nextPlayer; - } - } -} - -void TableFileGame::AddPlayer(const std::string &p_name) -{ - auto *player = new TableFilePlayer; - player->m_name = p_name; - - if (m_firstPlayer) { - m_lastPlayer->m_next = player; - m_lastPlayer = player; - } - else { - m_firstPlayer = player; - m_lastPlayer = player; + size_t NumPlayers() const { return m_players.size(); } + std::vector NumStrategies() const + { + 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; } - m_numPlayers++; -} -int TableFileGame::NumStrategies(int p_player) const -{ - TableFilePlayer *player = m_firstPlayer; - int pl = 1; - - while (player && pl < p_player) { - player = player->m_next; - pl++; + std::string GetPlayer(int p_player) const { return m_players[p_player - 1].m_name; } + std::string GetStrategy(int p_player, int p_strategy) const + { + return m_players[p_player - 1].m_strategies[p_strategy]; } +}; - if (!player) { - return 0; - } - else { - return player->m_strategies.Length(); - } -} - -std::string TableFileGame::GetPlayer(int p_player) const -{ - TableFilePlayer *player = m_firstPlayer; - int pl = 1; - - while (player && pl < p_player) { - player = player->m_next; - pl++; - } - - if (!player) { - return ""; - } - else { - return player->m_name; - } -} - -std::string TableFileGame::GetStrategy(int p_player, int p_strategy) const -{ - TableFilePlayer *player = m_firstPlayer; - int pl = 1; - - while (player && pl < p_player) { - player = player->m_next; - pl++; - } - - if (!player) { - return ""; - } - else { - return player->m_strategies[p_strategy]; - } -} - -void ReadPlayers(GameParserState &p_state, TableFileGame &p_data) +void ReadPlayers(GameFileLexer &p_state, TableFileGame &p_data) { - if (p_state.GetNextToken() != TOKEN_LBRACE) { - throw InvalidFileException(p_state.CreateLineMsg("Expecting '{' before players")); - } - + p_state.ExpectNextToken(TOKEN_LBRACE, "'{'"); while (p_state.GetNextToken() == TOKEN_TEXT) { - p_data.AddPlayer(p_state.GetLastText()); + p_data.m_players.emplace_back(p_state.GetLastText()); } - - if (p_state.GetCurrentToken() != TOKEN_RBRACE) { - throw InvalidFileException(p_state.CreateLineMsg("Expecting '}' after players")); - } - - p_state.GetNextToken(); + p_state.ExpectCurrentToken(TOKEN_RBRACE, "'}'"); } -void ReadStrategies(GameParserState &p_state, TableFileGame &p_data) +void ReadStrategies(GameFileLexer &p_state, TableFileGame &p_data) { - if (p_state.GetCurrentToken() != TOKEN_LBRACE) { - throw InvalidFileException(p_state.CreateLineMsg("Expecting '{' before strategies")); - } - p_state.GetNextToken(); - - if (p_state.GetCurrentToken() == TOKEN_LBRACE) { - TableFilePlayer *player = p_data.m_firstPlayer; - - while (p_state.GetCurrentToken() == TOKEN_LBRACE) { - if (!player) { - throw InvalidFileException( - p_state.CreateLineMsg("Not enough players for number of strategy entries")); - } + p_state.ExpectNextToken(TOKEN_LBRACE, "'{'"); + auto token = p_state.GetNextToken(); + if (token == TOKEN_LBRACE) { + // Nested list of strategy labels + for (auto &player : p_data.m_players) { + p_state.ExpectCurrentToken(TOKEN_LBRACE, "'{'"); while (p_state.GetNextToken() == TOKEN_TEXT) { - player->m_strategies.push_back(p_state.GetLastText()); - } - - if (p_state.GetCurrentToken() != TOKEN_RBRACE) { - throw InvalidFileException(p_state.CreateLineMsg("Expecting '}' after player strategy")); + player.m_strategies.push_back(p_state.GetLastText()); } - + p_state.ExpectCurrentToken(TOKEN_RBRACE, "'}'"); p_state.GetNextToken(); - player = player->m_next; - } - - if (player) { - throw InvalidFileException(p_state.CreateLineMsg("Players with undefined strategies")); } - - if (p_state.GetCurrentToken() != TOKEN_RBRACE) { - throw InvalidFileException(p_state.CreateLineMsg("Expecting '}' after strategies")); - } - - p_state.GetNextToken(); } - else if (p_state.GetCurrentToken() == TOKEN_NUMBER) { - TableFilePlayer *player = p_data.m_firstPlayer; - - while (p_state.GetCurrentToken() == TOKEN_NUMBER) { - if (!player) { - throw InvalidFileException( - p_state.CreateLineMsg("Not enough players for number of strategy entries")); - } - - for (int st = 1; st <= atoi(p_state.GetLastText().c_str()); st++) { - player->m_strategies.push_back(lexical_cast(st)); + else { + // List of number of strategies for each player, no labels + for (auto &player : p_data.m_players) { + p_state.ExpectCurrentToken(TOKEN_NUMBER, "number"); + for (int st = 1; st <= std::stoi(p_state.GetLastText()); st++) { + player.m_strategies.push_back(std::to_string(st)); } - p_state.GetNextToken(); - player = player->m_next; - } - - if (p_state.GetCurrentToken() != TOKEN_RBRACE) { - throw InvalidFileException(p_state.CreateLineMsg("Expecting '}' after strategies")); - } - - if (player) { - throw InvalidFileException(p_state.CreateLineMsg("Players with strategies undefined")); } - - p_state.GetNextToken(); - } - else { - throw InvalidFileException(p_state.CreateLineMsg("Unrecognizable strategies format")); } + p_state.ExpectCurrentToken(TOKEN_RBRACE, "'}'"); + p_state.GetNextToken(); } -void ParseNfgHeader(GameParserState &p_state, TableFileGame &p_data) +void ParseNfgHeader(GameFileLexer &p_state, TableFileGame &p_data) { + if (p_state.GetNextToken() != TOKEN_SYMBOL || p_state.GetLastText() != "NFG") { + p_state.OnParseError("Expecting NFG file type indicator"); + } if (p_state.GetNextToken() != TOKEN_NUMBER || p_state.GetLastText() != "1") { - throw InvalidFileException(p_state.CreateLineMsg("Accepting only NFG version 1")); + p_state.OnParseError("Accepting only NFG version 1"); } - if (p_state.GetNextToken() != TOKEN_SYMBOL || (p_state.GetLastText() != "D" && p_state.GetLastText() != "R")) { - throw InvalidFileException(p_state.CreateLineMsg("Accepting only NFG D or R data type")); + p_state.OnParseError("Accepting only NFG D or R data type"); } if (p_state.GetNextToken() != TOKEN_TEXT) { - throw InvalidFileException(p_state.CreateLineMsg("Game title missing")); + p_state.OnParseError("Game title missing"); } p_data.m_title = p_state.GetLastText(); @@ -499,141 +387,80 @@ void ParseNfgHeader(GameParserState &p_state, TableFileGame &p_data) } } -void ReadOutcomeList(GameParserState &p_parser, Game &p_nfg) +void ReadOutcomeList(GameFileLexer &p_parser, Game &p_nfg) { - if (p_parser.GetNextToken() == TOKEN_RBRACE) { - // Special case: empty outcome list - p_parser.GetNextToken(); - return; - } - - if (p_parser.GetCurrentToken() != TOKEN_LBRACE) { - throw InvalidFileException(p_parser.CreateLineMsg("Expecting '{' before outcome")); - } - - int nOutcomes = 0; + auto players = p_nfg->GetPlayers(); + p_parser.GetNextToken(); while (p_parser.GetCurrentToken() == TOKEN_LBRACE) { - nOutcomes++; - int pl = 1; - - if (p_parser.GetNextToken() != TOKEN_TEXT) { - throw InvalidFileException(p_parser.CreateLineMsg("Expecting string for outcome")); - } - - GameOutcome outcome; - try { - outcome = p_nfg->GetOutcome(nOutcomes); - } - catch (IndexException &) { - // It might happen that the file contains more outcomes than - // contingencies. If so, just create them on the fly. - outcome = p_nfg->NewOutcome(); - } + p_parser.ExpectNextToken(TOKEN_TEXT, "outcome name"); + auto outcome = p_nfg->NewOutcome(); outcome->SetLabel(p_parser.GetLastText()); p_parser.GetNextToken(); - try { - while (p_parser.GetCurrentToken() == TOKEN_NUMBER) { - outcome->SetPayoff(pl++, Number(p_parser.GetLastText())); - if (p_parser.GetNextToken() == TOKEN_COMMA) { - p_parser.GetNextToken(); - } - } - } - catch (IndexException &) { - // This would be triggered by too many payoffs - throw InvalidFileException(p_parser.CreateLineMsg("Exceeded number of players in outcome")); - } - - if (pl <= p_nfg->NumPlayers() || p_parser.GetCurrentToken() != TOKEN_RBRACE) { - throw InvalidFileException( - p_parser.CreateLineMsg("Insufficient number of players in outcome")); + for (auto player : players) { + p_parser.ExpectCurrentToken(TOKEN_NUMBER, "numerical payoff"); + outcome->SetPayoff(player, Number(p_parser.GetLastText())); + p_parser.AcceptNextToken(TOKEN_COMMA); } - + p_parser.ExpectCurrentToken(TOKEN_RBRACE, "'}'"); p_parser.GetNextToken(); } - if (p_parser.GetCurrentToken() != TOKEN_RBRACE) { - throw InvalidFileException(p_parser.CreateLineMsg("Expecting '}' after outcome")); - } + p_parser.ExpectCurrentToken(TOKEN_RBRACE, "'}'"); p_parser.GetNextToken(); } -void ParseOutcomeBody(GameParserState &p_parser, Game &p_nfg) +void ParseOutcomeBody(GameFileLexer &p_parser, Game &p_nfg) { ReadOutcomeList(p_parser, p_nfg); - - StrategySupportProfile profile(p_nfg); - StrategyProfileIterator iter(profile); - - while (p_parser.GetCurrentToken() != TOKEN_EOF) { - if (p_parser.GetCurrentToken() != TOKEN_NUMBER) { - throw InvalidFileException(p_parser.CreateLineMsg("Expecting outcome index")); - } - - int outcomeId = atoi(p_parser.GetLastText().c_str()); - if (outcomeId > 0) { - (*iter)->SetOutcome(p_nfg->GetOutcome(outcomeId)); - } - else { - (*iter)->SetOutcome(nullptr); + const StrategySupportProfile profile(p_nfg); + for (auto iter : StrategyContingencies(profile)) { + p_parser.ExpectCurrentToken(TOKEN_NUMBER, "outcome index"); + if (const int outcomeId = std::stoi(p_parser.GetLastText())) { + iter->SetOutcome(p_nfg->GetOutcome(outcomeId)); } p_parser.GetNextToken(); - iter++; } } -void ParsePayoffBody(GameParserState &p_parser, Game &p_nfg) +void ParsePayoffBody(GameFileLexer &p_parser, Game &p_nfg) { - StrategySupportProfile profile(p_nfg); - StrategyProfileIterator iter(profile); - int pl = 1; - - while (p_parser.GetCurrentToken() != TOKEN_EOF) { - if (p_parser.GetCurrentToken() == TOKEN_NUMBER) { - (*iter)->GetOutcome()->SetPayoff(pl, Number(p_parser.GetLastText())); - } - else { - throw InvalidFileException(p_parser.CreateLineMsg("Expecting payoff")); - } - - if (++pl > p_nfg->NumPlayers()) { - iter++; - pl = 1; + const StrategySupportProfile profile(p_nfg); + for (auto iter : StrategyContingencies(profile)) { + 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(); } - p_parser.GetNextToken(); } } -Game BuildNfg(GameParserState &p_parser, TableFileGame &p_data) +Game BuildNfg(GameFileLexer &p_parser, TableFileGame &p_data) { - Array dim(p_data.NumPlayers()); - for (int pl = 1; pl <= dim.Length(); pl++) { - dim[pl] = p_data.NumStrategies(pl); + if (p_parser.GetCurrentToken() != TOKEN_LBRACE && p_parser.GetCurrentToken() != TOKEN_NUMBER) { + p_parser.OnParseError("Expecting outcome or payoff"); } - Game nfg = NewTable(dim); + // If this looks lke an outcome-based format, then don't create outcomes in advance + Game nfg = NewTable(p_data.NumStrategies(), p_parser.GetCurrentToken() == TOKEN_LBRACE); nfg->SetTitle(p_data.m_title); nfg->SetComment(p_data.m_comment); - for (int pl = 1; pl <= dim.Length(); pl++) { - nfg->GetPlayer(pl)->SetLabel(p_data.GetPlayer(pl)); - for (int st = 1; st <= dim[pl]; st++) { - nfg->GetPlayer(pl)->GetStrategy(st)->SetLabel(p_data.GetStrategy(pl, st)); + for (auto player : nfg->GetPlayers()) { + player->SetLabel(p_data.GetPlayer(player->GetNumber())); + for (auto strategy : player->GetStrategies()) { + strategy->SetLabel(p_data.GetStrategy(player->GetNumber(), strategy->GetNumber())); } } if (p_parser.GetCurrentToken() == TOKEN_LBRACE) { ParseOutcomeBody(p_parser, nfg); } - else if (p_parser.GetCurrentToken() == TOKEN_NUMBER) { - ParsePayoffBody(p_parser, nfg); - } else { - throw InvalidFileException(p_parser.CreateLineMsg("Expecting outcome or payoff")); + ParsePayoffBody(p_parser, nfg); } - + p_parser.ExpectCurrentToken(TOKEN_EOF, "end-of-file"); return nfg; } @@ -644,276 +471,180 @@ Game BuildNfg(GameParserState &p_parser, TableFileGame &p_data) class TreeData { public: std::map m_outcomeMap; - std::map m_chanceInfosetMap; - List> m_infosetMap; - - TreeData() = default; - ~TreeData() = default; + std::map> m_infosetMap; }; -void ReadPlayers(GameParserState &p_state, Game &p_game, TreeData &p_treeData) +void ReadPlayers(GameFileLexer &p_state, Game &p_game, TreeData &p_treeData) { - if (p_state.GetNextToken() != TOKEN_LBRACE) { - throw InvalidFileException(p_state.CreateLineMsg("Expecting '{' before players")); - } - + p_state.ExpectNextToken(TOKEN_LBRACE, "'{'"); while (p_state.GetNextToken() == TOKEN_TEXT) { p_game->NewPlayer()->SetLabel(p_state.GetLastText()); - p_treeData.m_infosetMap.push_back(std::map()); - } - - if (p_state.GetCurrentToken() != TOKEN_RBRACE) { - throw InvalidFileException(p_state.CreateLineMsg("Expecting '}' after players")); } + p_state.ExpectCurrentToken(TOKEN_RBRACE, "'}'"); } -// -// Precondition: Parser state should be expecting the integer index -// of the outcome in a node entry -// -// Postcondition: Parser state is past the outcome entry and should be -// pointing to the 'c', 'p', or 't' token starting the -// next node declaration. -// -void ParseOutcome(GameParserState &p_state, Game &p_game, TreeData &p_treeData, GameNode &p_node) +void ParseOutcome(GameFileLexer &p_state, Game &p_game, TreeData &p_treeData, GameNode &p_node) { - if (p_state.GetCurrentToken() != TOKEN_NUMBER) { - throw InvalidFileException(p_state.CreateLineMsg("Expecting index of outcome")); - } - - int outcomeId = atoi(p_state.GetLastText().c_str()); + p_state.ExpectCurrentToken(TOKEN_NUMBER, "index of outcome"); + const int outcomeId = std::stoi(p_state.GetLastText()); p_state.GetNextToken(); if (p_state.GetCurrentToken() == TOKEN_TEXT) { // This node entry contains information about the outcome GameOutcome outcome; - if (p_treeData.m_outcomeMap.count(outcomeId)) { - outcome = p_treeData.m_outcomeMap[outcomeId]; + try { + outcome = p_treeData.m_outcomeMap.at(outcomeId); } - else { + catch (std::out_of_range &) { outcome = p_game->NewOutcome(); p_treeData.m_outcomeMap[outcomeId] = outcome; } - outcome->SetLabel(p_state.GetLastText()); - p_node->SetOutcome(outcome); + p_game->SetOutcome(p_node, outcome); - if (p_state.GetNextToken() != TOKEN_LBRACE) { - throw InvalidFileException(p_state.CreateLineMsg("Expecting '{' before outcome")); - } + p_state.ExpectNextToken(TOKEN_LBRACE, "'{'"); p_state.GetNextToken(); - - for (int pl = 1; pl <= p_game->NumPlayers(); pl++) { - if (p_state.GetCurrentToken() == TOKEN_NUMBER) { - outcome->SetPayoff(pl, Number(p_state.GetLastText())); - } - else { - throw InvalidFileException(p_state.CreateLineMsg("Payoffs should be numbers")); - } - - // Commas are optional between payoffs - if (p_state.GetNextToken() == TOKEN_COMMA) { - p_state.GetNextToken(); - } - } - - if (p_state.GetCurrentToken() != TOKEN_RBRACE) { - throw InvalidFileException(p_state.CreateLineMsg("Expecting '}' after outcome")); + for (auto player : p_game->GetPlayers()) { + p_state.ExpectCurrentToken(TOKEN_NUMBER, "numerical payoff"); + outcome->SetPayoff(player, Number(p_state.GetLastText())); + p_state.AcceptNextToken(TOKEN_COMMA); } + p_state.ExpectCurrentToken(TOKEN_RBRACE, "'}'"); p_state.GetNextToken(); } else if (outcomeId != 0) { // The node entry does not contain information about the outcome. - // This means the outcome better have been defined already; - // if not, raise an error. - if (p_treeData.m_outcomeMap.count(outcomeId)) { - p_node->SetOutcome(p_treeData.m_outcomeMap[outcomeId]); + // This means the outcome should have been defined already. + try { + p_game->SetOutcome(p_node, p_treeData.m_outcomeMap.at(outcomeId)); } - else { - throw InvalidFileException(p_state.CreateLineMsg("Outcome not defined")); + catch (std::out_of_range) { + p_state.OnParseError("Outcome not defined"); } } } -void ParseNode(GameParserState &p_state, Game p_game, GameNode p_node, TreeData &p_treeData); +void ParseNode(GameFileLexer &p_state, Game p_game, GameNode p_node, TreeData &p_treeData); -// -// Precondition: parser state is expecting the node label -// -// Postcondition: parser state is pointing at the 'c', 'p', or 't' -// beginning the next node entry -// -void ParseChanceNode(GameParserState &p_state, Game &p_game, GameNode &p_node, - TreeData &p_treeData) +void ParseChanceNode(GameFileLexer &p_state, Game &p_game, GameNode &p_node, TreeData &p_treeData) { - if (p_state.GetNextToken() != TOKEN_TEXT) { - throw InvalidFileException(p_state.CreateLineMsg("Expecting label")); - } + p_state.ExpectNextToken(TOKEN_TEXT, "node label"); p_node->SetLabel(p_state.GetLastText()); - if (p_state.GetNextToken() != TOKEN_NUMBER) { - throw InvalidFileException(p_state.CreateLineMsg("Expecting infoset id")); - } - - int infosetId = atoi(p_state.GetLastText().c_str()); - GameInfoset infoset; - if (p_treeData.m_chanceInfosetMap.count(infosetId)) { - infoset = p_treeData.m_chanceInfosetMap[infosetId]; - } - - p_state.GetNextToken(); + p_state.ExpectNextToken(TOKEN_NUMBER, "infoset id"); + const int infosetId = atoi(p_state.GetLastText().c_str()); + GameInfoset infoset = p_treeData.m_infosetMap[0][infosetId]; - if (p_state.GetCurrentToken() == TOKEN_TEXT) { + if (p_state.GetNextToken() == TOKEN_TEXT) { // Information set data is specified - List actions, probs; - std::string label = p_state.GetLastText(); + std::list action_labels; + Array probs; - if (p_state.GetNextToken() != TOKEN_LBRACE) { - throw InvalidFileException( - p_state.CreateLineMsg("Expecting '{' before information set data")); - } + const std::string label = p_state.GetLastText(); + p_state.ExpectNextToken(TOKEN_LBRACE, "'{'"); p_state.GetNextToken(); do { - if (p_state.GetCurrentToken() != TOKEN_TEXT) { - throw InvalidFileException(p_state.CreateLineMsg("Expecting action")); - } - actions.push_back(p_state.GetLastText()); - - p_state.GetNextToken(); - - if (p_state.GetCurrentToken() == TOKEN_NUMBER) { - probs.push_back(p_state.GetLastText()); - } - else { - throw InvalidFileException(p_state.CreateLineMsg("Expecting probability")); - } - + p_state.ExpectCurrentToken(TOKEN_TEXT, "action label"); + action_labels.push_back(p_state.GetLastText()); + p_state.ExpectNextToken(TOKEN_NUMBER, "action probability"); + probs.push_back(Number(p_state.GetLastText())); p_state.GetNextToken(); } while (p_state.GetCurrentToken() != TOKEN_RBRACE); p_state.GetNextToken(); if (!infoset) { - infoset = p_node->AppendMove(p_game->GetChance(), actions.Length()); - p_treeData.m_chanceInfosetMap[infosetId] = infoset; + infoset = p_game->AppendMove(p_node, p_game->GetChance(), action_labels.size()); + p_treeData.m_infosetMap[0][infosetId] = infoset; infoset->SetLabel(label); - for (int act = 1; act <= actions.Length(); act++) { - infoset->GetAction(act)->SetLabel(actions[act]); - } - Array prob_numbers(probs.size()); - for (int act = 1; act <= actions.Length(); act++) { - prob_numbers[act] = Number(probs[act]); + auto action_label = action_labels.begin(); + for (auto action : infoset->GetActions()) { + action->SetLabel(*action_label); + ++action_label; } - p_game->SetChanceProbs(infoset, prob_numbers); + p_game->SetChanceProbs(infoset, probs); } else { - // TODO: Verify actions match up to previous specifications - p_node->AppendMove(infoset); + // TODO: Verify actions match up to any previous specifications + p_game->AppendMove(p_node, infoset); } } else if (infoset) { - p_node->AppendMove(infoset); + p_game->AppendMove(p_node, infoset); } else { - // Referencing an undefined infoset is an error - throw InvalidFileException(p_state.CreateLineMsg("Referencing an undefined infoset")); + p_state.OnParseError("Referencing an undefined infoset"); } ParseOutcome(p_state, p_game, p_treeData, p_node); - - for (int i = 1; i <= p_node->NumChildren(); i++) { - ParseNode(p_state, p_game, p_node->GetChild(i), p_treeData); + for (auto child : p_node->GetChildren()) { + ParseNode(p_state, p_game, child, p_treeData); } } -void ParsePersonalNode(GameParserState &p_state, Game p_game, GameNode p_node, - TreeData &p_treeData) +void ParsePersonalNode(GameFileLexer &p_state, Game p_game, GameNode p_node, TreeData &p_treeData) { - if (p_state.GetNextToken() != TOKEN_TEXT) { - throw InvalidFileException(p_state.CreateLineMsg("Expecting label")); - } + p_state.ExpectNextToken(TOKEN_TEXT, "node label"); p_node->SetLabel(p_state.GetLastText()); - if (p_state.GetNextToken() != TOKEN_NUMBER) { - throw InvalidFileException(p_state.CreateLineMsg("Expecting player id")); - } - int playerId = atoi(p_state.GetLastText().c_str()); - // This will throw an exception if the player ID is not valid - GamePlayer player = p_game->GetPlayer(playerId); - std::map &infosetMap = p_treeData.m_infosetMap[playerId]; - - if (p_state.GetNextToken() != TOKEN_NUMBER) { - throw InvalidFileException(p_state.CreateLineMsg("Expecting infoset id")); - } - int infosetId = atoi(p_state.GetLastText().c_str()); - GameInfoset infoset; - if (infosetMap.count(infosetId)) { - infoset = infosetMap[infosetId]; - } + p_state.ExpectNextToken(TOKEN_NUMBER, "player id"); + const int playerId = std::stoi(p_state.GetLastText()); + const GamePlayer player = p_game->GetPlayer(playerId); - p_state.GetNextToken(); + p_state.ExpectNextToken(TOKEN_NUMBER, "infoset id"); + const int infosetId = std::stoi(p_state.GetLastText()); + GameInfoset infoset = p_treeData.m_infosetMap[playerId][infosetId]; - if (p_state.GetCurrentToken() == TOKEN_TEXT) { + if (p_state.GetNextToken() == TOKEN_TEXT) { // Information set data is specified - List actions; - std::string label = p_state.GetLastText(); + std::list action_labels; + const std::string label = p_state.GetLastText(); - if (p_state.GetNextToken() != TOKEN_LBRACE) { - throw InvalidFileException( - p_state.CreateLineMsg("Expecting '{' before information set data")); - } + p_state.ExpectNextToken(TOKEN_LBRACE, "'{'"); p_state.GetNextToken(); do { - if (p_state.GetCurrentToken() != TOKEN_TEXT) { - throw InvalidFileException(p_state.CreateLineMsg("Expecting action")); - } - actions.push_back(p_state.GetLastText()); - + p_state.ExpectCurrentToken(TOKEN_TEXT, "action label"); + action_labels.push_back(p_state.GetLastText()); p_state.GetNextToken(); } while (p_state.GetCurrentToken() != TOKEN_RBRACE); p_state.GetNextToken(); if (!infoset) { - infoset = p_node->AppendMove(player, actions.Length()); - infosetMap[infosetId] = infoset; + infoset = p_game->AppendMove(p_node, player, action_labels.size()); + p_treeData.m_infosetMap[playerId][infosetId] = infoset; infoset->SetLabel(label); - for (int act = 1; act <= actions.Length(); act++) { - infoset->GetAction(act)->SetLabel(actions[act]); + auto action_label = action_labels.begin(); + for (auto action : infoset->GetActions()) { + action->SetLabel(*action_label); + ++action_label; } } 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 { - // Referencing an undefined infoset is an error - throw InvalidFileException(p_state.CreateLineMsg("Referencing an undefined infoset")); + p_state.OnParseError("Referencing an undefined infoset"); } ParseOutcome(p_state, p_game, p_treeData, p_node); - - for (int i = 1; i <= p_node->NumChildren(); i++) { - ParseNode(p_state, p_game, p_node->GetChild(i), p_treeData); + for (auto child : p_node->GetChildren()) { + ParseNode(p_state, p_game, child, p_treeData); } } -void ParseTerminalNode(GameParserState &p_state, Game p_game, GameNode p_node, - TreeData &p_treeData) +void ParseTerminalNode(GameFileLexer &p_state, Game p_game, GameNode p_node, TreeData &p_treeData) { - if (p_state.GetNextToken() != TOKEN_TEXT) { - throw InvalidFileException(p_state.CreateLineMsg("Expecting label")); - } - + p_state.ExpectNextToken(TOKEN_TEXT, "node label"); p_node->SetLabel(p_state.GetLastText()); - p_state.GetNextToken(); ParseOutcome(p_state, p_game, p_treeData, p_node); } -void ParseNode(GameParserState &p_state, Game p_game, GameNode p_node, TreeData &p_treeData) +void ParseNode(GameFileLexer &p_state, Game p_game, GameNode p_node, TreeData &p_treeData) { if (p_state.GetLastText() == "c") { ParseChanceNode(p_state, p_game, p_node, p_treeData); @@ -925,34 +656,8 @@ void ParseNode(GameParserState &p_state, Game p_game, GameNode p_node, TreeData ParseTerminalNode(p_state, p_game, p_node, p_treeData); } else { - throw InvalidFileException(p_state.CreateLineMsg("Invalid type of node")); - } -} - -void ParseEfg(GameParserState &p_state, Game p_game, TreeData &p_treeData) -{ - if (p_state.GetNextToken() != TOKEN_NUMBER || p_state.GetLastText() != "2") { - throw InvalidFileException(p_state.CreateLineMsg("Accepting only EFG version 2")); - } - - if (p_state.GetNextToken() != TOKEN_SYMBOL || - (p_state.GetLastText() != "D" && p_state.GetLastText() != "R")) { - throw InvalidFileException(p_state.CreateLineMsg("Accepting only EFG R or D data type")); - } - if (p_state.GetNextToken() != TOKEN_TEXT) { - throw InvalidFileException(p_state.CreateLineMsg("Game title missing")); + p_state.OnParseError("Invalid type of node"); } - p_game->SetTitle(p_state.GetLastText()); - - ReadPlayers(p_state, p_game, p_treeData); - - if (p_state.GetNextToken() == TOKEN_TEXT) { - // Read optional comment - p_game->SetComment(p_state.GetLastText()); - p_state.GetNextToken(); - } - - ParseNode(p_state, p_game, p_game->GetRoot(), p_treeData); } } // end of anonymous namespace @@ -1007,9 +712,53 @@ Game GameXMLSavefile::GetGame() const throw InvalidFileException("No game representation found in document"); } -//========================================================================= -// ReadGame: Global visible function to read an .efg or .nfg file -//========================================================================= +Game ReadEfgFile(std::istream &p_stream) +{ + GameFileLexer parser(p_stream); + + if (parser.GetNextToken() != TOKEN_SYMBOL || parser.GetLastText() != "EFG") { + parser.OnParseError("Expecting EFG file type indicator"); + } + if (parser.GetNextToken() != TOKEN_NUMBER || parser.GetLastText() != "2") { + parser.OnParseError("Accepting only EFG version 2"); + } + if (parser.GetNextToken() != TOKEN_SYMBOL || + (parser.GetLastText() != "D" && parser.GetLastText() != "R")) { + parser.OnParseError("Accepting only EFG R or D data type"); + } + if (parser.GetNextToken() != TOKEN_TEXT) { + parser.OnParseError("Game title missing"); + } + + TreeData treeData; + Game game = NewTree(); + dynamic_cast(*game).SetCanonicalization(false); + game->SetTitle(parser.GetLastText()); + ReadPlayers(parser, game, treeData); + if (parser.GetNextToken() == TOKEN_TEXT) { + // Read optional comment + game->SetComment(parser.GetLastText()); + parser.GetNextToken(); + } + ParseNode(parser, game, game->GetRoot(), treeData); + dynamic_cast(*game).SetCanonicalization(true); + return game; +} + +Game ReadNfgFile(std::istream &p_stream) +{ + GameFileLexer parser(p_stream); + TableFileGame data; + ParseNfgHeader(parser, data); + return BuildNfg(parser, data); +} + +Game ReadGbtFile(std::istream &p_stream) +{ + std::stringstream buffer; + buffer << p_stream.rdbuf(); + return GameXMLSavefile(buffer.str()).GetGame(); +} Game ReadGame(std::istream &p_file) { @@ -1019,41 +768,32 @@ Game ReadGame(std::istream &p_file) throw InvalidFileException("Empty file or string provided"); } try { - GameXMLSavefile doc(buffer.str()); - return doc.GetGame(); + return ReadGbtFile(buffer); } catch (InvalidFileException &) { buffer.seekg(0, std::ios::beg); } - GameParserState parser(buffer); + GameFileLexer parser(buffer); try { if (parser.GetNextToken() != TOKEN_SYMBOL) { - throw InvalidFileException(parser.CreateLineMsg("Expecting file type")); + parser.OnParseError("Expecting file type"); } - + buffer.seekg(0, std::ios::beg); if (parser.GetLastText() == "NFG") { - TableFileGame data; - ParseNfgHeader(parser, data); - return BuildNfg(parser, data); + return ReadNfgFile(buffer); } else if (parser.GetLastText() == "EFG") { - TreeData treeData; - Game game = NewTree(); - dynamic_cast(*game).SetCanonicalization(false); - ParseEfg(parser, game, treeData); - dynamic_cast(*game).SetCanonicalization(true); - return game; + return ReadEfgFile(buffer); } else if (parser.GetLastText() == "#AGG") { - return GameAGGRep::ReadAggFile(buffer); + return ReadAggFile(buffer); } else if (parser.GetLastText() == "#BAGG") { - return GameBAGGRep::ReadBaggFile(buffer); + return ReadBaggFile(buffer); } else { - throw InvalidFileException( - "Tokens 'EFG' or 'NFG' or '#AGG' or '#BAGG' expected at start of file"); + throw InvalidFileException("Unrecognized file format"); } } catch (std::exception &ex) { diff --git a/src/games/game.cc b/src/games/game.cc index 79ff4fc26..8a2f9be0a 100644 --- a/src/games/game.cc +++ b/src/games/game.cc @@ -21,17 +21,16 @@ // #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 { @@ -40,31 +39,24 @@ 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(); + } } //======================================================================== // class GameStrategyRep //======================================================================== -void GameStrategyRep::DeleteStrategy() +GameAction GameStrategyRep::GetAction(const GameInfoset &p_infoset) const { - if (m_player->GetGame()->IsTree()) { - throw UndefinedException(); - } - if (m_player->NumStrategies() == 1) { - return; + if (p_infoset->GetPlayer() != m_player) { + throw MismatchException(); } - - m_player->GetGame()->IncrementVersion(); - m_player->m_strategies.Remove(m_player->m_strategies.Find(this)); - for (int st = 1; st <= m_player->m_strategies.Length(); st++) { - m_player->m_strategies[st]->m_number = st; - } - // m_player->m_game->RebuildTable(); - this->Invalidate(); + const int action = m_behav[p_infoset->GetNumber()]; + return (action) ? *std::next(p_infoset->GetActions().cbegin(), action - 1) : nullptr; } //======================================================================== @@ -72,11 +64,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, "")); } } @@ -90,52 +81,25 @@ GamePlayerRep::~GamePlayerRep() } } -Array GamePlayerRep::GetStrategies() const -{ - 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; -} - -GameStrategy GamePlayerRep::NewStrategy() -{ - if (m_game->IsTree()) { - throw UndefinedException(); - } - - 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() { - Array c(NumInfosets()); + Array c(m_infosets.size()); - for (int i = 1; i <= NumInfosets(); i++) { - if (m_infosets[i]->flag == 1) { - c[i] = m_infosets[i]->whichbranch; + for (size_t i = 1; i <= m_infosets.size(); i++) { + if (m_infosets[i - 1]->flag == 1) { + c[i] = m_infosets[i - 1]->whichbranch; } else { c[i] = 0; } } - auto *strategy = new GameStrategyRep(this); - m_strategies.push_back(strategy); - strategy->m_number = m_strategies.Length(); + auto *strategy = new GameStrategyRep(this, m_strategies.size() + 1, ""); 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.Length(); iset++) { + for (size_t iset = 1; iset <= strategy->m_behav.size(); iset++) { if (strategy->m_behav[iset] > 0) { strategy->m_label += lexical_cast(strategy->m_behav[iset]); } @@ -147,33 +111,33 @@ void GamePlayerRep::MakeStrategy() else { strategy->m_label = "*"; } + m_strategies.push_back(strategy); } -void GamePlayerRep::MakeReducedStrats(GameTreeNodeRep *n, GameTreeNodeRep *nn) +void GamePlayerRep::MakeReducedStrats(GameNodeRep *n, GameNodeRep *nn) { - int i; - GameTreeNodeRep *m, *mm; + GameNodeRep *m; if (!n->GetParent()) { n->ptr = nullptr; } - if (n->NumChildren() > 0) { - if (n->infoset->m_player == this) { - if (n->infoset->flag == 0) { + if (!n->IsTerminal()) { + if (n->m_infoset->m_player == this) { + if (n->m_infoset->flag == 0) { // we haven't visited this infoset before - n->infoset->flag = 1; - for (i = 1; i <= n->NumChildren(); i++) { - GameTreeNodeRep *m = n->children[i]; + n->m_infoset->flag = 1; + for (size_t i = 1; i <= n->m_children.size(); i++) { + GameNodeRep *m = n->m_children[i - 1]; n->whichbranch = m; - n->infoset->whichbranch = i; + n->m_infoset->whichbranch = i; MakeReducedStrats(m, nn); } - n->infoset->flag = 0; + n->m_infoset->flag = 0; } else { // we have visited this infoset, take same action - MakeReducedStrats(n->children[n->infoset->whichbranch], nn); + MakeReducedStrats(n->m_children[n->m_infoset->whichbranch - 1], nn); } } else { @@ -181,11 +145,11 @@ void GamePlayerRep::MakeReducedStrats(GameTreeNodeRep *n, GameTreeNodeRep *nn) if (nn != nullptr) { n->ptr = nn->m_parent; } - n->whichbranch = n->children[1]; - if (n->infoset) { - n->infoset->whichbranch = 0; + n->whichbranch = n->m_children.front(); + if (n->m_infoset) { + n->m_infoset->whichbranch = 0; } - MakeReducedStrats(n->children[1], n->children[1]); + MakeReducedStrats(n->m_children.front(), n->m_children.front()); } } else if (nn) { @@ -194,14 +158,14 @@ void GamePlayerRep::MakeReducedStrats(GameTreeNodeRep *n, GameTreeNodeRep *nn) m = nullptr; } else { - m = dynamic_cast(nn->GetNextSibling().operator->()); + m = nn->GetNextSibling(); } if (m || nn->m_parent->ptr == nullptr) { break; } } if (m) { - mm = m->m_parent->whichbranch; + GameNodeRep *mm = m->m_parent->whichbranch; m->m_parent->whichbranch = m; MakeReducedStrats(m, m); m->m_parent->whichbranch = mm; @@ -215,74 +179,33 @@ void GamePlayerRep::MakeReducedStrats(GameTreeNodeRep *n, GameTreeNodeRep *nn) } } -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; } //------------------------------------------------------------------------ // GameRep: Writing data files //------------------------------------------------------------------------ -namespace { - -std::string EscapeQuotes(const std::string &s) -{ - std::string ret; - - for (char c : s) { - if (c == '"') { - ret += '\\'; - } - ret += c; - } - - return ret; -} - -} // end anonymous namespace - /// /// Write the game to a savefile in .nfg payoff format. /// @@ -297,31 +220,29 @@ std::string EscapeQuotes(const std::string &s) void GameRep::WriteNfgFile(std::ostream &p_file) const { auto players = GetPlayers(); - p_file << "NFG 1 R"; - p_file << " \"" << EscapeQuotes(GetTitle()) << "\" { "; + p_file << "NFG 1 R " << std::quoted(GetTitle()) << ' ' + << FormatList(players, [](const GamePlayer &p) { return QuoteString(p->GetLabel()); }) + << std::endl + << std::endl; + p_file << "{ "; for (auto player : players) { - p_file << '"' << EscapeQuotes(player->GetLabel()) << "\" "; + p_file << FormatList(player->GetStrategies(), [](const GameStrategy &s) { + return QuoteString(s->GetLabel()); + }) << std::endl; } - p_file << "}\n\n{ "; - - for (auto player : players) { - p_file << "{ "; - for (auto strategy : player->GetStrategies()) { - p_file << '"' << EscapeQuotes(strategy->GetLabel()) << "\" "; - } - p_file << "}\n"; - } - p_file << "}\n"; - p_file << "\"" << EscapeQuotes(m_comment) << "\"\n\n"; - - for (StrategyProfileIterator iter(StrategySupportProfile(Game(const_cast(this)))); - !iter.AtEnd(); iter++) { - for (auto player : players) { - p_file << (*iter)->GetPayoff(player) << " "; - } - p_file << "\n"; - } - p_file << '\n'; + p_file << "}" << std::endl; + p_file << std::quoted(GetComment()) << std::endl << std::endl; + + for (auto iter : + StrategyContingencies(StrategySupportProfile(Game(const_cast(this))))) { + p_file << FormatList( + players, + [&iter](const GamePlayer &p) { + return lexical_cast(iter->GetPayoff(p)); + }, + false, false) + << std::endl; + }; } //======================================================================== @@ -333,13 +254,24 @@ MixedStrategyProfileRep::MixedStrategyProfileRep(const StrategySupportProfile : m_probs(p_support.MixedProfileLength()), m_support(p_support), m_gameversion(p_support.GetGame()->GetVersion()) { + int index = 1; + for (auto player : p_support.GetGame()->GetPlayers()) { + for (auto strategy : player->GetStrategies()) { + if (p_support.Contains(strategy)) { + m_profileIndex[strategy] = index++; + } + else { + m_profileIndex[strategy] = -1; + } + } + } SetCentroid(); } template void MixedStrategyProfileRep::SetCentroid() { for (auto player : m_support.GetGame()->GetPlayers()) { - T center = ((T)1) / ((T)m_support.NumStrategies(player->GetNumber())); + T center = T(1) / T(m_support.GetStrategies(player).size()); for (auto strategy : m_support.GetStrategies(player)) { (*this)[strategy] = center; } @@ -350,11 +282,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)) { @@ -366,7 +298,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()) { @@ -405,46 +337,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->GetNumber()] > 0) { + prob *= p_profile[infoset->GetAction(strategy->m_behav[infoset->GetNumber()])]; } } - (*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 //======================================================================== @@ -454,7 +371,7 @@ template Vector MixedStrategyProfile::operator[](const GamePlaye CheckVersion(); auto strategies = m_rep->m_support.GetStrategies(p_player); Vector probs(strategies.size()); - std::transform(strategies.cbegin(), strategies.cend(), probs.begin(), + std::transform(strategies.begin(), strategies.end(), probs.begin(), [this](const GameStrategy &s) { return (*m_rep)[s]; }); return probs; } @@ -464,24 +381,19 @@ template MixedStrategyProfile MixedStrategyProfile::ToFullSuppor CheckVersion(); MixedStrategyProfile full(m_rep->m_support.GetGame()->NewMixedStrategyProfile(T(0))); - 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++) { - if (m_rep->m_support.Contains(player->GetStrategy(st))) { - full[player->GetStrategy(st)] = (*this)[player->GetStrategy(st)]; - } - else { - full[player->GetStrategy(st)] = static_cast(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; } //======================================================================== // MixedStrategyProfile: Computation of interesting quantities //======================================================================== + template void MixedStrategyProfile::ComputePayoffs() const { if (!map_profile_payoffs.empty()) { @@ -489,10 +401,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); } } @@ -503,13 +415,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 3953929bd..0edae9120 100644 --- a/src/games/game.h +++ b/src/games/game.h @@ -23,12 +23,10 @@ #ifndef LIBGAMBIT_GAME_H #define LIBGAMBIT_GAME_H -#include #include #include -#include +#include -#include "core/dvector.h" #include "number.h" #include "gameobject.h" @@ -58,6 +56,81 @@ 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; + } + + bool empty() const { return m_container->empty(); } + 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. // @@ -118,11 +191,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 //@{ @@ -144,12 +216,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); //@} @@ -157,61 +225,89 @@ 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; + int flag{0}, whichbranch{0}; + 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. @@ -238,18 +334,17 @@ class GameStrategyRep : public GameObject { template friend class TableMixedStrategyProfileRep; template friend class MixedBehaviorProfile; -private: - int m_number, m_id; GamePlayerRep *m_player; - long m_offset; + int m_number; + long m_offset{-1L}; std::string m_label; Array m_behav; /// @name Lifecycle //@{ /// Creates a new strategy for the given player. - explicit GameStrategyRep(GamePlayerRep *p_player) - : m_number(0), m_id(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) { } //@} @@ -266,24 +361,22 @@ class GameStrategyRep : public GameObject { GamePlayer GetPlayer() const; /// Returns the index of the strategy for its player int GetNumber() const { return m_number; } - /// Returns the global number of the strategy in the game - int GetId() const { return m_id; } - /// Remove this strategy from the game - void DeleteStrategy(); + /// Returns the action specified by the strategy at the information set + GameAction GetAction(const GameInfoset &) const; //@} }; /// A player in a game 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; @@ -291,21 +384,23 @@ class GamePlayerRep : public GameObject { /// @name Building reduced form strategies //@{ void MakeStrategy(); - void MakeReducedStrats(class GameTreeNodeRep *, class GameTreeNodeRep *); + void MakeReducedStrats(class GameNodeRep *, class GameNodeRep *); //@} -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; @@ -316,92 +411,86 @@ 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; + GameNodeRep *whichbranch{nullptr}, *ptr{nullptr}; - virtual int GetNumber() const = 0; - virtual int NumberInInfoset() 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; @@ -409,23 +498,110 @@ 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 { + Game m_owner{nullptr}; + + public: + class iterator { + friend class Nodes; + using ChildIterator = ElementCollection::iterator; + + Game m_owner{nullptr}; + GameNode m_current_node{nullptr}; + std::stack> m_stack{}; + + iterator(const Game &game) : m_owner(game) {} + + public: + using iterator_category = std::forward_iterator_tag; + using value_type = GameNode; + using pointer = value_type *; + + iterator() = default; + + iterator(const Game &game, const GameNode &start_node) : m_owner(game), m_current_node(start_node) + { + if (!start_node) { + return; + } + if (start_node->GetGame() != m_owner) { + throw MismatchException(); + } + } + + value_type operator*() const + { + if (!m_current_node) { + throw std::runtime_error("Cannot dereference an end iterator"); + } + return m_current_node; + } + + iterator &operator++() + { + if (!m_current_node) { + throw std::out_of_range("Cannot increment an end iterator"); + } + + if (!m_current_node->IsTerminal()) { + auto children = m_current_node->GetChildren(); + m_stack.emplace(children.begin(), children.end()); + } + + while (!m_stack.empty()) { + auto &[current_it, end_it] = m_stack.top(); + + if (current_it != end_it) { + m_current_node = *current_it; + ++current_it; + return *this; + } + m_stack.pop(); + } + + m_current_node = nullptr; + return *this; + } + + bool operator==(const iterator &other) const + { + return m_owner == other.m_owner && m_current_node == other.m_current_node; + } + bool operator!=(const iterator &other) const { return !(*this == other); } + }; + + Nodes() = default; + explicit Nodes(const Game &p_owner) : m_owner(p_owner) {} + + iterator begin() const + { + return (m_owner) ? iterator{m_owner, m_owner->GetRoot()} : iterator{}; + } + iterator end() const { return (m_owner) ? iterator{m_owner} : iterator{}; } + }; + /// @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; //@} @@ -435,7 +611,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 @@ -454,10 +630,21 @@ 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 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. If not, /// a pair of violating information sets is returned in the parameters. @@ -478,29 +665,79 @@ class GameRep : public BaseGameRep { throw UndefinedException(); } /// Write the game in .efg format to the specified stream - virtual void WriteEfgFile(std::ostream &) const { throw UndefinedException(); } + virtual void WriteEfgFile(std::ostream &, const GameNode &subtree = nullptr) const + { + throw UndefinedException(); + } /// Write the game to a file in .nfg payoff format. 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 actions in each information set - virtual PVector NumActions() const = 0; - /// The number of members in each information set - virtual PVector NumMembers() const = 0; /// 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; @@ -537,11 +774,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 @@ -551,33 +788,33 @@ 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 //@{ /// Returns the root node of the game virtual GameNode GetRoot() const = 0; + /// Returns a range that can be used to iterate over the nodes of the game + Nodes GetNodes() const { return Nodes(this); } /// 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 @@ -585,6 +822,9 @@ class GameRep : public BaseGameRep { /// Set the probability distribution of actions at a chance node virtual Game SetChanceProbs(const GameInfoset &, const Array &) = 0; //@} + + /// Build any computed values anew + virtual void BuildComputedValues() const {} }; //======================================================================= @@ -595,18 +835,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) @@ -614,39 +861,75 @@ 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); -/// Reads a game in .efg or .nfg format from the input stream -Game ReadGame(std::istream &); +Game NewTable(const std::vector &p_dim, bool p_sparseOutcomes = false); + +/// @brief Reads a game representation in .efg format +/// +/// @param[in] p_stream An input stream, positioned at the start of the text in .efg format +/// @return A handle to the game representation constructed +/// @throw InvalidFileException If the stream does not contain a valid serialisation +/// of a game in .efg format. +/// @sa Game::WriteEfgFile, ReadNfgFile, ReadAggFile, ReadBaggFile +Game ReadEfgFile(std::istream &p_stream); + +/// @brief Reads a game representation in .nfg format +/// @param[in] p_stream An input stream, positioned at the start of the text in .nfg format +/// @return A handle to the game representation constructed +/// @throw InvalidFileException If the stream does not contain a valid serialisation +/// of a game in .nfg format. +/// @sa Game::WriteNfgFile, ReadEfgFile, ReadAggFile, ReadBaggFile +Game ReadNfgFile(std::istream &p_stream); + +/// @brief Reads a game representation from a graphical interface XML saveflie +/// @param[in] p_stream An input stream, positioned at the start of the text +/// @return A handle to the game representation constructed +/// @throw InvalidFileException If the stream does not contain a valid serialisation +/// of a game in an XML savefile +/// @sa ReadEfgFile, ReadNfgFile, ReadAggFile, ReadBaggFile +Game ReadGbtFile(std::istream &p_stream); + +/// @brief Reads a game from the input stream, attempting to autodetect file format +/// @deprecated Deprecated in favour of the various ReadXXXGame functions. +/// @sa ReadEfgFile, ReadNfgFile, ReadGbtFile, ReadAggFile, ReadBaggFile +Game ReadGame(std::istream &p_stream); /// @brief Generate a distribution over a simplex restricted to rational numbers of given /// denominator 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 57a683f5b..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_support.m_profileIndex[strategy->GetId()]; + 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,8 +122,9 @@ 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 int &ind = this->m_support.m_profileIndex[strategy->GetId()]; + 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); - const int &ind = this->m_support.m_profileIndex[strategy->GetId()]; + 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,15 +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)); - } - } - for (int pl = 1, id = 1; pl <= m_players.Length(); pl++) { - for (int st = 1; st <= m_players[pl]->m_strategies.Length(); - m_players[pl]->m_strategies[st++]->m_id = id++) - ; + 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++)); }); } } @@ -207,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(); @@ -232,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 @@ -264,18 +259,16 @@ GameAGGRep::NewMixedStrategyProfile(const Rational &, const StrategySupportProfi bool GameAGGRep::IsConstSum() const { - AGGPureStrategyProfileRep profile(const_cast(this)); - + auto profile = NewPureStrategyProfile(); Rational sum(0); - for (int pl = 1; pl <= m_players.Length(); pl++) { - sum += profile.GetPayoff(pl); + for (const auto &player : m_players) { + sum += profile->GetPayoff(player); } - for (StrategyProfileIterator iter(StrategySupportProfile(const_cast(this))); - !iter.AtEnd(); iter++) { + for (auto iter : StrategyContingencies(Game(this))) { Rational newsum(0); - for (int pl = 1; pl <= m_players.Length(); pl++) { - newsum += (*iter)->GetPayoff(pl); + for (const auto &player : m_players) { + newsum += iter->GetPayoff(player); } if (newsum != sum) { return false; @@ -331,6 +324,4 @@ void GameAGGRep::WriteAggFile(std::ostream &s) const } } -Game GameAGGRep::ReadAggFile(std::istream &in) { return new GameAGGRep(agg::AGG::makeAGG(in)); } - } // end namespace Gambit diff --git a/src/games/gameagg.h b/src/games/gameagg.h index 4b8778706..79fb14e4c 100644 --- a/src/games/gameagg.h +++ b/src/games/gameagg.h @@ -33,23 +33,12 @@ class GameAGGRep : public GameRep { private: std::shared_ptr aggPtr; - Array m_players; - - /// Constructor - explicit GameAGGRep(std::shared_ptr); public: /// @name Lifecycle //@{ - /// Create a game from a serialized file in AGG format - static Game ReadAggFile(std::istream &); - /// Destructor - ~GameAGGRep() override - { - for (auto player : m_players) { - player->Invalidate(); - } - } + /// Constructor + explicit GameAGGRep(std::shared_ptr); /// Create a copy of the game, as a new game Game Copy() const override; //@} @@ -57,19 +46,12 @@ class GameAGGRep : public GameRep { std::shared_ptr GetUnderlyingAGG() const { return aggPtr; } /// @name Dimensions of the game //@{ - /// The number of actions in each information set - PVector NumActions() const override { throw UndefinedException(); } - /// The number of members in each information set - PVector NumMembers() const override { throw UndefinedException(); } /// The number of strategies for each player Array NumStrategies() const override; /// Gets the i'th strategy in the game, numbered globally 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; @@ -82,46 +64,20 @@ 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 @@ -130,11 +86,14 @@ class GameAGGRep : public GameRep { bool IsAgg() const override { return true; } bool IsPerfectRecall(GameInfoset &, GameInfoset &) 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 @@ -149,10 +108,25 @@ class GameAGGRep : public GameRep { //@{ /// Write the game to a savefile in the specified format. void Write(std::ostream &p_stream, const std::string &p_format = "native") const override; - virtual void WriteAggFile(std::ostream &) const; + void WriteAggFile(std::ostream &) const; //@} }; +/// @brief Reads a game representation in .agg format +/// @param[in] p_stream An input stream, positioned at the start of the text in .agg format +/// @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 &p_stream) +{ + try { + return new GameAGGRep(agg::AGG::makeAGG(p_stream)); + } + catch (std::runtime_error &ex) { + throw InvalidFileException(ex.what()); + } +} + } // namespace Gambit #endif // GAMEAGG_H diff --git a/src/games/gamebagg.cc b/src/games/gamebagg.cc index 8bdb0b109..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) +Rational BAGGPureStrategyProfileRep::GetPayoff(const GamePlayer &p_player) const { - m_profile[s->GetPlayer()->GetNumber()] = s; -} - -Rational BAGGPureStrategyProfileRep::GetPayoff(int pl) 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); - const int &ind = this->m_support.m_profileIndex[strategy->GetId()]; + 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); - const int &ind = this->m_support.m_profileIndex[strategy->GetId()]; + 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); - const int &ind = this->m_support.m_profileIndex[strategy->GetId()]; + 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,18 +213,12 @@ 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++)); }); } } - for (int pl = 1, id = 1; pl <= m_players.Length(); pl++) { - for (int st = 1; st <= m_players[pl]->m_strategies.Length(); - m_players[pl]->m_strategies[st++]->m_id = id++) - ; - } } Game GameBAGGRep::Copy() const @@ -245,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.Length()); + 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.Length(); - } - 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 @@ -307,9 +290,4 @@ void GameBAGGRep::Write(std::ostream &p_stream, const std::string &p_format /*=" void GameBAGGRep::WriteBaggFile(std::ostream &s) const { s << (*baggPtr); } -Game GameBAGGRep::ReadBaggFile(std::istream &in) -{ - return new GameBAGGRep(agg::BAGG::makeBAGG(in)); -} - } // end namespace Gambit diff --git a/src/games/gamebagg.h b/src/games/gamebagg.h index 20c9b2f33..9e7dedc66 100644 --- a/src/games/gamebagg.h +++ b/src/games/gamebagg.h @@ -34,16 +34,12 @@ class GameBAGGRep : public GameRep { private: std::shared_ptr baggPtr; Array agent2baggPlayer; - Array m_players; - - /// Constructor - explicit GameBAGGRep(std::shared_ptr _baggPtr); public: /// @name Lifecycle //@{ - /// Create a game from a serialized file in BAGG format - static Game ReadBaggFile(std::istream &); + /// Constructor + explicit GameBAGGRep(std::shared_ptr _baggPtr); /// Destructor ~GameBAGGRep() override { @@ -57,20 +53,12 @@ class GameBAGGRep : public GameRep { /// @name Dimensions of the game //@{ - /// The number of actions in each information set - PVector NumActions() const override { throw UndefinedException(); } - /// The number of members in each information set - PVector NumMembers() const override { throw UndefinedException(); } /// The number of strategies for each player 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; @@ -83,46 +71,20 @@ 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 @@ -131,10 +93,14 @@ class GameBAGGRep : public GameRep { virtual bool IsBagg() const { return true; } bool IsPerfectRecall(GameInfoset &, GameInfoset &) 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 @@ -154,6 +120,21 @@ class GameBAGGRep : public GameRep { //@} }; +/// @brief Reads a game representation in .bagg format +/// @param[in] p_stream An input stream, positioned at the start of the text in .bagg format +/// @return A handle to the game representation constructed +/// @throw InvalidFileException If the stream does not contain a valid serialisation +/// of a game in .bagg format. +inline Game ReadBaggFile(std::istream &in) +{ + try { + return new GameBAGGRep(agg::BAGG::makeBAGG(in)); + } + catch (std::runtime_error &ex) { + throw InvalidFileException(ex.what()); + } +} + } // end namespace Gambit #endif // GAMEBAGG_H diff --git a/src/games/gameexpl.cc b/src/games/gameexpl.cc index b05bb889b..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 (int 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.Length(); pl++) { - for (int st = 1; st <= m_players[pl]->m_strategies.Length(); 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 683e008e3..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; @@ -148,7 +148,7 @@ template class GameObjectPtr { T *rep; public: - GameObjectPtr(T *r = nullptr) : rep(r) + GameObjectPtr(const T *r = nullptr) : rep(const_cast(r)) { if (rep) { rep->IncRef(); diff --git a/src/games/gametable.cc b/src/games/gametable.cc index 2a3b7825c..5ae53b04a 100644 --- a/src/games/gametable.cc +++ b/src/games/gametable.cc @@ -24,6 +24,7 @@ #include "gambit.h" #include "gametable.h" +#include "writer.h" namespace Gambit { @@ -33,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; }; @@ -54,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); } @@ -75,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 @@ -89,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); @@ -102,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); @@ -116,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))); } //======================================================================== @@ -157,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); @@ -180,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 @@ -190,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 { @@ -211,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; } @@ -223,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 { @@ -253,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; } @@ -264,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.Length(); 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.Length(); m_results[cont++] = 0) - ; + std::fill(m_results.begin(), m_results.end(), nullptr); } else { - m_outcomes = Array(m_results.Length()); - for (int i = 1; i <= m_outcomes.Length(); 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()); } } @@ -317,25 +302,21 @@ Game GameTableRep::Copy() const bool GameTableRep::IsConstSum() const { - TablePureStrategyProfileRep profile(const_cast(this)); - + auto profile = NewPureStrategyProfile(); Rational sum(0); - for (int pl = 1; pl <= m_players.Length(); pl++) { - sum += profile.GetPayoff(pl); + for (const auto &player : m_players) { + sum += profile->GetPayoff(player); } - for (StrategyProfileIterator iter(StrategySupportProfile(const_cast(this))); - !iter.AtEnd(); iter++) { + for (auto iter : StrategyContingencies(Game(this))) { Rational newsum(0); - for (int pl = 1; pl <= m_players.Length(); pl++) { - newsum += (*iter)->GetPayoff(pl); + for (const auto &player : m_players) { + newsum += iter->GetPayoff(player); } - if (newsum != sum) { return false; } } - return true; } @@ -343,24 +324,6 @@ bool GameTableRep::IsConstSum() const // GameTableRep: Writing data files //------------------------------------------------------------------------ -namespace { - -std::string EscapeQuotes(const std::string &s) -{ - std::string ret; - - for (char c : s) { - if (c == '"') { - ret += '\\'; - } - ret += c; - } - - return ret; -} - -} // end anonymous namespace - /// /// Write the game to a savefile in .nfg outcome format. /// @@ -372,58 +335,35 @@ std::string EscapeQuotes(const std::string &s) /// void GameTableRep::WriteNfgFile(std::ostream &p_file) const { - p_file << "NFG 1 R"; - p_file << " \"" << EscapeQuotes(GetTitle()) << "\" { "; - - for (int i = 1; i <= NumPlayers(); i++) { - p_file << '"' << EscapeQuotes(GetPlayer(i)->GetLabel()) << "\" "; - } - - p_file << "}\n\n{ "; - - for (int i = 1; i <= NumPlayers(); i++) { - GamePlayerRep *player = GetPlayer(i); - p_file << "{ "; - for (int j = 1; j <= player->NumStrategies(); j++) { - p_file << '"' << EscapeQuotes(player->GetStrategy(j)->GetLabel()) << "\" "; - } - p_file << "}\n"; - } - - p_file << "}\n"; - - p_file << "\"" << EscapeQuotes(m_comment) << "\"\n\n"; - - int ncont = 1; - for (int i = 1; i <= NumPlayers(); i++) { - ncont *= m_players[i]->m_strategies.Length(); - } - - p_file << "{\n"; + auto players = GetPlayers(); + p_file << "NFG 1 R " << std::quoted(GetTitle()) << ' ' + << FormatList(players, [](const GamePlayer &p) { return QuoteString(p->GetLabel()); }) + << std::endl + << std::endl; + p_file << "{ "; + for (auto player : players) { + p_file << FormatList(player->GetStrategies(), [](const GameStrategy &s) { + return QuoteString(s->GetLabel()); + }) << std::endl; + } + p_file << "}" << std::endl; + p_file << std::quoted(GetComment()) << std::endl << std::endl; + + p_file << "{" << std::endl; for (auto outcome : m_outcomes) { - p_file << "{ \"" << EscapeQuotes(outcome->m_label) << "\" "; - for (int pl = 1; pl <= m_players.Length(); pl++) { - p_file << (const std::string &)outcome->m_payoffs[pl]; - if (pl < m_players.Length()) { - p_file << ", "; - } - else { - p_file << " }\n"; - } - } + p_file << "{ " + QuoteString(outcome->GetLabel()) << ' ' + << FormatList( + players, + [outcome](const GamePlayer &p) { return outcome->GetPayoff(p); }, + true, false) + << " }" << std::endl; } - p_file << "}\n"; + p_file << "}" << std::endl; - for (int cont = 1; cont <= ncont; cont++) { - if (m_results[cont] != 0) { - p_file << m_results[cont]->m_number << ' '; - } - else { - p_file << "0 "; - } + for (auto result : m_results) { + p_file << ((result) ? result->m_number : 0) << ' '; } - - p_file << '\n'; + p_file << std::endl; } //------------------------------------------------------------------------ @@ -436,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; } @@ -448,15 +388,47 @@ GamePlayer GameTableRep::NewPlayer() void GameTableRep::DeleteOutcome(const GameOutcome &p_outcome) { IncrementVersion(); - for (int i = 1; i <= m_results.Length(); i++) { - if (m_results[i] == p_outcome) { - m_results[i] = 0; - } + 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)); + 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(); } - m_outcomes.Remove(m_outcomes.Find(p_outcome))->Invalidate(); - for (int outc = 1; outc <= m_outcomes.Length(); outc++) { - m_outcomes[outc]->m_number = outc; + 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(); } //------------------------------------------------------------------------ @@ -496,37 +468,34 @@ GameTableRep::NewMixedStrategyProfile(const Rational &, const StrategySupportPro void GameTableRep::RebuildTable() { long size = 1L; - Array offsets(m_players.Length()); - for (int pl = 1; pl <= m_players.Length(); 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.Length(); newResults[i++] = 0) - ; + std::vector newResults(size); + std::fill(newResults.begin(), newResults.end(), nullptr); - for (StrategyProfileIterator iter(StrategySupportProfile(const_cast(this))); - !iter.AtEnd(); iter++) { - long newindex = 1L; - for (int pl = 1; pl <= m_players.Length(); pl++) { - if (iter.m_profile->GetStrategy(pl)->m_offset < 0) { + for (auto iter : + StrategyContingencies(StrategySupportProfile(const_cast(this)))) { + 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.m_profile->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.m_profile->GetIndex()]; + if (newindex >= 0) { + newResults[newindex] = iter->GetOutcome(); } } - - m_results = newResults; - + m_results.swap(newResults); IndexStrategies(); } @@ -542,13 +511,6 @@ void GameTableRep::IndexStrategies() } offset *= player->m_strategies.size(); } - - int id = 1; - for (auto player : m_players) { - for (auto strategy : player->m_strategies) { - strategy->m_id = id++; - } - } } } // end namespace Gambit diff --git a/src/games/gametable.h b/src/games/gametable.h index f99f29126..d9fb2bb96 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; //@} @@ -62,10 +62,6 @@ class GameTableRep : public GameExplicitRep { /// @name Dimensions of the game //@{ - /// The number of actions in each information set - PVector NumActions() const override { throw UndefinedException(); } - /// The number of members in each information set - PVector NumMembers() const override { throw UndefinedException(); } /// Returns the total number of actions in the game int BehavProfileLength() const override { throw UndefinedException(); } //@} @@ -78,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 @@ -104,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 d4407a63d..7bb28e3f8 100644 --- a/src/games/gametree.cc +++ b/src/games/gametree.cc @@ -26,6 +26,7 @@ #include "gambit.h" #include "gametree.h" +#include "writer.h" namespace Gambit { @@ -49,13 +50,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 @@ -63,54 +63,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 { @@ -120,168 +112,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.Length() && m_infoset->m_actions[where] != this; - where++) - ; - - m_infoset->RemoveAction(where); - for (auto member : m_infoset->m_members) { - member->children[where]->DeleteTree(); - member->children.Remove(where)->Invalidate(); + 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.Length())); - for (int act = 1; act <= m_actions.Length(); 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.Remove(m_player->m_infosets.Find(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.Length() + 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(action, where); - if (m_player->IsChance()) { - m_probs.Insert(Number("0"), where); - } + 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.Length(); 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.Length(); i++) { - m_members[i]->children.Insert(new GameTreeNodeRep(m_efg, m_members[i]), where); + 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) -{ - m_efg->IncrementVersion(); - m_actions.Remove(which)->Invalidate(); - if (m_player->IsChance()) { - m_probs.Remove(which); - } - for (; which <= m_actions.Length(); which++) { - m_actions[which]->m_number = which; - } -} - -void GameTreeInfosetRep::RemoveMember(GameTreeNodeRep *p_node) +void GameTreeRep::RemoveMember(GameInfosetRep *p_infoset, GameNodeRep *p_node) { - m_efg->IncrementVersion(); - m_members.Remove(m_members.Find(p_node)); - if (m_members.Length() == 0) { - m_player->m_infosets.Remove(m_player->m_infosets.Find(this)); - for (int i = 1; i <= m_player->m_infosets.Length(); i++) { - m_player->m_infosets[i]->m_number = i; + 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 : p_infoset->m_player->m_infosets) { + infoset->m_number = iset++; } - Invalidate(); + 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) @@ -290,134 +264,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.Length(); 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) -{ -} - -GameTreeNodeRep::~GameTreeNodeRep() -{ - std::for_each(children.begin(), children.end(), [](GameNodeRep *n) { n->Invalidate(); }); -} - -Game GameTreeNodeRep::GetGame() const { return m_efg; } +GameNodeRep::GameNodeRep(GameRep *e, GameNodeRep *p) : m_game(e), m_parent(p) {} -Array GameTreeNodeRep::GetChildren() const +GameNodeRep::~GameNodeRep() { - Array ret(children.size()); - std::transform(children.cbegin(), children.cend(), ret.begin(), - [](GameTreeNodeRep *n) -> GameNode { return n; }); - return ret; + std::for_each(m_children.begin(), m_children.end(), [](GameNodeRep *n) { n->Invalidate(); }); } -GameNode GameTreeNodeRep::GetNextSibling() const +GameNode GameNodeRep::GetNextSibling() const { - if (!m_parent) { - return nullptr; - } - if (m_parent->children.back() == this) { + if (!m_parent || m_parent->m_children.back() == this) { return nullptr; } - else { - return m_parent->children[m_parent->children.Find(const_cast(this)) + 1]; - } + 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) { - return nullptr; - } - if (m_parent->children.front() == this) { + if (!m_parent || m_parent->m_children.front() == this) { return nullptr; } - else { - return m_parent->children[m_parent->children.Find(const_cast(this)) - 1]; - } + 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) { @@ -427,14 +369,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; } } } @@ -442,253 +385,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.Remove(oldParent->children.Find(this)); - oldParent->DeleteTree(); - m_parent = oldParent->m_parent; - if (m_parent) { - m_parent->children[m_parent->children.Find(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(); - children.Remove(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.Length()) { - AppendMove(src->infoset); - for (int i = 1; i <= src->children.Length(); 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.Length(); 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->()); - - if (src->m_parent == m_parent) { - int srcChild = src->m_parent->children.Find(src); - int destChild = src->m_parent->children.Find(this); - src->m_parent->children[srcChild] = this; - src->m_parent->children[destChild] = src; - } - else { - GameTreeNodeRep *parent = src->m_parent; - parent->children[parent->children.Find(src)] = this; - m_parent->children[m_parent->children.Find(this)] = src; - src->m_parent = m_parent; - m_parent = 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.Length()) { + 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; - if (oldInfoset->m_members.Length() == 1) { + 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.Length() + 1, player, children.Length()); - infoset->AddMember(this); - for (int i = 1; i <= oldInfoset->m_actions.Length(); 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.Length() + 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) { - m_parent->children[m_parent->children.Find(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; } @@ -701,8 +666,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)) { } @@ -734,22 +698,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; @@ -771,23 +735,23 @@ bool GameTreeRep::IsConstSum() const bool GameTreeRep::IsPerfectRecall(GameInfoset &s1, GameInfoset &s2) 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]; + for (size_t i = 1; i <= player->m_infosets.size(); i++) { + auto *iset1 = player->m_infosets[i - 1]; + for (size_t j = 1; j <= player->m_infosets.size(); j++) { + auto *iset2 = player->m_infosets[j - 1]; bool precedes = false; - int action = 0; + GameAction action = nullptr; - for (int m = 1; m <= iset2->NumMembers(); m++) { - int n; - for (n = 1; n <= iset1->NumMembers(); n++) { + for (size_t m = 1; m <= iset2->m_members.size(); m++) { + size_t n; + for (n = 1; n <= iset1->m_members.size(); 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++) { + for (const auto &act : iset1->GetActions()) { if (iset2->GetMember(m)->IsSuccessorOf(iset1->GetMember(n)->GetChild(act))) { - if (action != 0 && action != act) { + if (action != nullptr && action != act) { s1 = iset1; s2 = iset2; return false; @@ -805,7 +769,7 @@ bool GameTreeRep::IsPerfectRecall(GameInfoset &s1, GameInfoset &s2) const return false; } - if (n > iset1->NumMembers() && precedes) { + if (n > iset1->m_members.size() && precedes) { s1 = iset1; s2 = iset2; return false; @@ -822,11 +786,12 @@ bool GameTreeRep::IsPerfectRecall(GameInfoset &s1, GameInfoset &s2) const // 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.Length(); NumberNodes(n->children[child++], index)) - ; + n->m_number = index++; + for (auto &child : n->m_children) { + NumberNodes(child, index); + } } void GameTreeRep::Canonicalize() @@ -837,20 +802,19 @@ void GameTreeRep::Canonicalize() int nodeindex = 1; NumberNodes(m_root, nodeindex); - for (int pl = 0; pl <= m_players.Length(); 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.Length(); iset++) { - GameTreeInfosetRep *infoset = player->m_infosets[iset]; - for (int i = 1; i < infoset->m_members.Length(); i++) { - for (int j = 1; j < infoset->m_members.Length() - 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; } } } @@ -859,60 +823,74 @@ 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.Length(); i++) { - for (int j = 1; j < player->m_infosets.Length() - i; j++) { - int a = ((player->m_infosets[j + 1]->m_members.Length()) - ? player->m_infosets[j + 1]->m_members[1]->number - : 0); - int b = ((player->m_infosets[j]->m_members.Length()) - ? 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.Length(); 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++; }); } } void GameTreeRep::ClearComputedValues() const { - for (int pl = 1; pl <= m_players.Length(); pl++) { - while (m_players[pl]->m_strategies.Length() > 0) { - m_players[pl]->m_strategies.Remove(1)->Invalidate(); + for (auto player : m_players) { + for (auto strategy : player->m_strategies) { + strategy->Invalidate(); } + player->m_strategies.clear(); } - + const_cast(this)->m_nodePlays.clear(); m_computedValues = false; } -void GameTreeRep::BuildComputedValues() +void GameTreeRep::BuildComputedValues() const { if (m_computedValues) { return; } + const_cast(this)->Canonicalize(); + for (const auto &player : m_players) { + player->MakeReducedStrats(m_root, nullptr); + } + m_computedValues = true; +} - Canonicalize(); +void GameTreeRep::BuildConsistentPlays() +{ + m_nodePlays.clear(); + BuildConsistentPlaysRecursiveImpl(m_root); +} - for (int pl = 1; pl <= m_players.Length(); pl++) { - m_players[pl]->MakeReducedStrats(m_root, nullptr); +std::vector GameTreeRep::BuildConsistentPlaysRecursiveImpl(GameNodeRep *node) +{ + std::vector consistent_plays; + if (node->IsTerminal()) { + consistent_plays = std::vector{node}; } - - for (int pl = 1, id = 1; pl <= m_players.Length(); pl++) { - for (int st = 1; st <= m_players[pl]->m_strategies.Length(); - m_players[pl]->m_strategies[st++]->m_id = id++) - ; + 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_computedValues = true; + m_nodePlays[node] = consistent_plays; + return consistent_plays; } //------------------------------------------------------------------------ @@ -921,127 +899,64 @@ void GameTreeRep::BuildComputedValues() namespace { -std::string EscapeQuotes(const std::string &s) +void WriteEfgFile(std::ostream &f, const GameNode &n) { - std::string ret; - - for (char c : s) { - if (c == '"') { - ret += '\\'; - } - ret += c; + if (n->IsTerminal()) { + f << "t "; } - - return ret; -} - -void PrintActions(std::ostream &p_stream, GameTreeInfosetRep *p_infoset) -{ - p_stream << "{ "; - for (int act = 1; act <= p_infoset->NumActions(); act++) { - p_stream << '"' << EscapeQuotes(p_infoset->GetAction(act)->GetLabel()) << "\" "; - if (p_infoset->IsChanceInfoset()) { - p_stream << static_cast(p_infoset->GetActionProb(act)) << ' '; - } + else if (n->GetInfoset()->IsChanceInfoset()) { + f << "c "; } - p_stream << "}"; -} - -void WriteEfgFile(std::ostream &f, GameTreeNodeRep *n) -{ - if (n->NumChildren() == 0) { - f << "t \"" << EscapeQuotes(n->GetLabel()) << "\" "; - if (n->GetOutcome()) { - f << n->GetOutcome()->GetNumber() << " \"" << EscapeQuotes(n->GetOutcome()->GetLabel()) - << "\" "; - f << "{ "; - for (int pl = 1; pl <= n->GetGame()->NumPlayers(); pl++) { - f << static_cast(n->GetOutcome()->GetPayoff(pl)); - - if (pl < n->GetGame()->NumPlayers()) { - f << ", "; - } - else { - f << " }\n"; - } - } + else { + f << "p "; + } + f << QuoteString(n->GetLabel()) << ' '; + if (!n->IsTerminal()) { + if (!n->GetInfoset()->IsChanceInfoset()) { + f << n->GetInfoset()->GetPlayer()->GetNumber() << ' '; + } + f << n->GetInfoset()->GetNumber() << " " << QuoteString(n->GetInfoset()->GetLabel()) << ' '; + if (n->GetInfoset()->IsChanceInfoset()) { + f << FormatList(n->GetInfoset()->GetActions(), [](const GameAction &a) { + return QuoteString(a->GetLabel()) + " " + std::string(a->GetInfoset()->GetActionProb(a)); + }); } else { - f << "0\n"; + f << FormatList(n->GetInfoset()->GetActions(), + [](const GameAction &a) { return QuoteString(a->GetLabel()); }); } - return; - } - - if (n->GetInfoset()->IsChanceInfoset()) { - f << "c \""; + f << ' '; } - else { - f << "p \""; - } - - f << EscapeQuotes(n->GetLabel()) << "\" "; - if (!n->GetInfoset()->IsChanceInfoset()) { - f << n->GetInfoset()->GetPlayer()->GetNumber() << ' '; - } - f << n->GetInfoset()->GetNumber() << " \"" << EscapeQuotes(n->GetInfoset()->GetLabel()) << "\" "; - PrintActions(f, dynamic_cast(n->GetInfoset().operator->())); - f << " "; if (n->GetOutcome()) { - f << n->GetOutcome()->GetNumber() << " \"" << EscapeQuotes(n->GetOutcome()->GetLabel()) - << "\" "; - f << "{ "; - for (int pl = 1; pl <= n->GetGame()->NumPlayers(); pl++) { - f << static_cast(n->GetOutcome()->GetPayoff(pl)); - - if (pl < n->GetGame()->NumPlayers()) { - f << ", "; - } - else { - f << " }\n"; - } - } + f << n->GetOutcome()->GetNumber() << " " << QuoteString(n->GetOutcome()->GetLabel()) << ' ' + << FormatList( + n->GetGame()->GetPlayers(), + [n](const GamePlayer &p) { return n->GetOutcome()->GetPayoff(p); }, true) + << std::endl; } else { - f << "0\n"; + f << "0" << std::endl; + } + for (auto child : n->GetChildren()) { + WriteEfgFile(f, child); } - - for (int i = 1; i <= n->NumChildren(); - WriteEfgFile(f, dynamic_cast(n->GetChild(i++).operator->()))) - ; } } // end anonymous namespace -void GameTreeRep::WriteEfgFile(std::ostream &p_file) const -{ - p_file << "EFG 2 R"; - p_file << " \"" << EscapeQuotes(GetTitle()) << "\" { "; - for (int i = 1; i <= m_players.Length(); i++) { - p_file << '"' << EscapeQuotes(m_players[i]->m_label) << "\" "; - } - p_file << "}\n"; - p_file << "\"" << EscapeQuotes(GetComment()) << "\"\n\n"; - - Gambit::WriteEfgFile(p_file, m_root); -} - -void GameTreeRep::WriteEfgFile(std::ostream &p_file, const GameNode &p_root) const +void GameTreeRep::WriteEfgFile(std::ostream &p_file, const GameNode &p_subtree /* =0 */) const { - p_file << "EFG 2 R"; - p_file << " \"" << EscapeQuotes(GetTitle()) << "\" { "; - for (int i = 1; i <= m_players.Length(); i++) { - p_file << '"' << EscapeQuotes(m_players[i]->m_label) << "\" "; - } - p_file << "}\n"; - p_file << "\"" << EscapeQuotes(GetComment()) << "\"\n\n"; - - Gambit::WriteEfgFile(p_file, dynamic_cast(p_root.operator->())); + p_file << "EFG 2 R " << std::quoted(GetTitle()) << ' ' + << FormatList(GetPlayers(), + [](const GamePlayer &p) { return QuoteString(p->GetLabel()); }) + << std::endl; + p_file << std::quoted(GetComment()) << std::endl << std::endl; + Gambit::WriteEfgFile(p_file, (p_subtree) ? p_subtree : GetRoot()); } void GameTreeRep::WriteNfgFile(std::ostream &p_file) const { - // FIXME: Building computed values is logically const. - const_cast(this)->BuildComputedValues(); + BuildComputedValues(); GameRep::WriteNfgFile(p_file); } @@ -1049,42 +964,6 @@ void GameTreeRep::WriteNfgFile(std::ostream &p_file) const // GameTreeRep: Dimensions of the game //------------------------------------------------------------------------ -PVector GameTreeRep::NumActions() const -{ - Array foo(m_players.Length()); - int i; - for (i = 1; i <= m_players.Length(); i++) { - foo[i] = m_players[i]->m_infosets.Length(); - } - - PVector bar(foo); - for (i = 1; i <= m_players.Length(); i++) { - for (int j = 1; j <= m_players[i]->m_infosets.Length(); j++) { - bar(i, j) = m_players[i]->m_infosets[j]->NumActions(); - } - } - - return bar; -} - -PVector GameTreeRep::NumMembers() const -{ - Array foo(m_players.Length()); - - for (int i = 1; i <= m_players.Length(); i++) { - foo[i] = m_players[i]->NumInfosets(); - } - - PVector bar(foo); - for (int i = 1; i <= m_players.Length(); i++) { - for (int j = 1; j <= m_players[i]->NumInfosets(); j++) { - bar(i, j) = m_players[i]->m_infosets[j]->NumMembers(); - } - } - - return bar; -} - int GameTreeRep::BehavProfileLength() const { int sum = 0; @@ -1103,11 +982,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(); outc++) { - m_outcomes[outc]->m_payoffs.push_back(Number()); + for (auto &outcome : m_outcomes) { + outcome->m_payoffs[player] = Number(); } ClearComputedValues(); return player; @@ -1130,76 +1008,67 @@ 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.Length()); - for (int i = 1; i <= foo.Length(); 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) { IncrementVersion(); m_root->DeleteOutcome(p_outcome); - m_outcomes.Remove(m_outcomes.Find(p_outcome))->Invalidate(); - for (int outc = 1; outc <= m_outcomes.Length(); outc++) { - m_outcomes[outc]->m_number = outc; - } + m_outcomes.erase(std::find(m_outcomes.begin(), m_outcomes.end(), p_outcome)); + p_outcome->Invalidate(); + 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 //------------------------------------------------------------------------ @@ -1213,56 +1082,47 @@ Game GameTreeRep::SetChanceProbs(const GameInfoset &p_infoset, const ArrayNumActions() != 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; } @@ -1311,14 +1171,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; }; @@ -1326,45 +1188,34 @@ 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) { + for (const auto &player : m_nfg->GetPlayers()) { + for (size_t iset = 1; iset <= player->GetInfosets().size(); iset++) { + if (const int act = m_profile.at(player)->m_behav[iset]) { behav.SetAction(player->GetInfoset(iset)->GetAction(act)); } } } - return behav.GetPayoff(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 73bddf034..f6c3f04b7 100644 --- a/src/games/gametree.h +++ b/src/games/gametree.h @@ -27,195 +27,22 @@ 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 NumberInInfoset() const override - { - return infoset->m_members.Find(const_cast(this)); - } - - 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; 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; /// @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 &); //@} @@ -223,8 +50,14 @@ class GameTreeRep : public GameExplicitRep { /// @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: @@ -264,24 +97,21 @@ 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 override; - virtual void WriteEfgFile(std::ostream &, const GameNode &p_node) const; + void WriteEfgFile(std::ostream &, const GameNode &p_node = nullptr) const override; void WriteNfgFile(std::ostream &) const override; //@} /// @name Dimensions of the game //@{ - /// The number of actions in each information set - PVector NumActions() const override; - /// The number of members in each information set - PVector NumMembers() const override; /// Returns the total number of actions in the game int BehavProfileLength() const override; //@} @@ -291,16 +121,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; @@ -310,6 +157,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 { @@ -326,10 +176,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 8329f4928..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 { @@ -224,15 +32,16 @@ namespace { /// Returns a list of the root nodes of all the immediate proper subgames /// in the subtree rooted at 'p_node'. /// -void ChildSubgames(const GameNode &p_node, List &p_list) +std::list ChildSubgames(const GameNode &p_node, const bool p_root = true) { - if (p_node->IsSubgameRoot()) { - p_list.push_back(p_node); + if (!p_root && p_node->IsSubgameRoot()) { + return {p_node}; } - else { - for (int i = 1; i <= p_node->NumChildren(); ChildSubgames(p_node->GetChild(i++), p_list)) - ; + std::list ret; + for (const auto &child : p_node->GetChildren()) { + ret.splice(ret.end(), ChildSubgames(child, false)); } + return ret; } } // namespace @@ -246,169 +55,149 @@ void ChildSubgames(const GameNode &p_node, List &p_list) // set to unique IDs. These are used to match up information // sets in the subgames (which are themselves copies) to the // original game. -// * We only carry around DVectors instead of full MixedBehaviorProfiles, -// because MixedBehaviorProfiles allocate space several times the -// size of the tree to carry around useful quantities. These -// quantities are irrelevant for this calculation, so we only -// store the probabilities, and convert to MixedBehaviorProfiles -// at the end of the computation // -template -void SubgameBehavSolver::SolveSubgames(const Game &p_game, const DVector &p_templateSolution, - const GameNode &n, List> &solns, - List &values) const -{ - List> thissolns; - thissolns.push_back(p_templateSolution); - ((Vector &)thissolns[1]).operator=(T(0)); +template class SubgameSolution { +private: + std::map profile; + std::map node_values; - List subroots; - for (int i = 1; i <= n->NumChildren(); i++) { - ChildSubgames(n->GetChild(i), subroots); +public: + SubgameSolution(const std::map &p_profile, + const std::map &p_nodeValues) + : profile(p_profile), node_values(p_nodeValues) + { } - List> subrootvalues; - subrootvalues.push_back(Array(subroots.Length())); - - for (int i = 1; i <= subroots.Length(); i++) { - // printf("Looking at subgame %d of %d\n", i, subroots.Length()); - List> subsolns; - List subvalues; - - SolveSubgames(p_game, p_templateSolution, subroots[i], subsolns, subvalues); + const std::map &GetProfile() const { return profile; } + const std::map &GetNodeValues() const { return node_values; } - if (subsolns.empty()) { - solns = List>(); - return; - } - - List> newsolns; - List> newsubrootvalues; + SubgameSolution Combine(const SubgameSolution &other) const + { + auto combined = *this; + combined.profile.insert(other.profile.cbegin(), other.profile.cend()); + combined.node_values.insert(other.node_values.cbegin(), other.node_values.cend()); + return combined; + } - for (int soln = 1; soln <= thissolns.Length(); soln++) { - for (int subsoln = 1; subsoln <= subsolns.Length(); subsoln++) { - DVector bp(thissolns[soln]); - DVector tmp(subsolns[subsoln]); - for (int j = 1; j <= bp.Length(); j++) { - bp[j] += tmp[j]; + SubgameSolution Update(const GameNode &p_subroot, const MixedBehaviorProfile &p_profile, + const std::map &p_infosetMap) + { + SubgameSolution solution = {profile, {{p_subroot, p_subroot->GetGame()->NewOutcome()}}}; + + for (const auto &subplayer : p_profile.GetGame()->GetPlayers()) { + for (const auto &subinfoset : subplayer->GetInfosets()) { + const GameInfoset infoset = p_infosetMap.at(subinfoset->GetLabel()); + const auto &subactions = subinfoset->GetActions(); + auto subaction = subactions.begin(); + for (const auto &action : infoset->GetActions()) { + solution.profile[action] = p_profile[*subaction]; + ++subaction; } - newsolns.push_back(bp); - - newsubrootvalues.push_back(subrootvalues[soln]); - newsubrootvalues[newsubrootvalues.Length()][i] = subvalues[subsoln]; } } - thissolns = newsolns; - subrootvalues = newsubrootvalues; - // printf("Finished solving subgame %d\n", i); + 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 += outcome->GetPayoff(*subplayer); + } + solution.node_values[p_subroot]->SetPayoff(player, Number(static_cast(value))); + ++subplayer; + } + return solution; } +}; - for (int soln = 1; soln <= thissolns.Length(); soln++) { - // printf("Analyzing scenario %d of %d\n", soln, thissolns.Length()); - for (int i = 1; i <= subroots.Length(); i++) { - subroots[i]->SetOutcome(subrootvalues[soln][i]); +template +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, p_solver)) { + for (const auto &subsolution : subsolutions) { + combined_solutions.push_back(subsolution.Combine(solution)); + } } - - Game subgame = n->CopySubgame(); - // this prevents double-counting of outcomes at roots of subgames - // by convention, we will just put the payoffs in the parent subgame - subgame->GetRoot()->SetOutcome(nullptr); - - BehaviorSupportProfile subsupport(subgame); - List> sol = m_solver->Solve(p_game); - - if (sol.empty()) { - solns = List>(); - return; + if (combined_solutions.empty()) { + return {}; } + subsolutions = combined_solutions; + } - // Put behavior profile in "total" solution here... - for (int solno = 1; solno <= sol.Length(); solno++) { - solns.push_back(thissolns[soln]); - - for (int pl = 1; pl <= subgame->NumPlayers(); pl++) { - GamePlayer subplayer = subgame->GetPlayer(pl); - GamePlayer player = p_game->GetPlayer(pl); - - for (int iset = 1; iset <= subplayer->NumInfosets(); iset++) { - GameInfoset subinfoset = subplayer->GetInfoset(iset); + std::list> solutions; + for (auto subsolution : subsolutions) { + for (auto [subroot, outcome] : subsolution.GetNodeValues()) { + 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. + const Game subgame = p_root->GetGame()->CopySubgame(p_root); + subgame->SetOutcome(subgame->GetRoot(), nullptr); - for (int j = 1; j <= player->NumInfosets(); j++) { - if (subinfoset->GetLabel() == player->GetInfoset(j)->GetLabel()) { - int id = atoi(subinfoset->GetLabel().c_str()); - for (int act = 1; act <= subsupport.NumActions(pl, iset); act++) { - int actno = subsupport.GetAction(pl, iset, act)->GetNumber(); - solns[solns.Length()](pl, id, actno) = sol[solno][subinfoset->GetAction(act)]; - } - break; - } - } - } - } + for (const auto &solution : p_solver(subgame)) { + solutions.push_back(subsolution.Update(p_root, solution, p_infosetMap)); + } + } - Vector subval(subgame->NumPlayers()); - GameOutcome outcome = n->GetOutcome(); - for (int pl = 1; pl <= subgame->NumPlayers(); pl++) { - subval[pl] = sol[solno].GetPayoff(pl); - if (outcome) { - subval[pl] += static_cast(outcome->GetPayoff(pl)); - } - } + p_root->GetGame()->DeleteTree(p_root); + return solutions; +} - GameOutcome ov = p_game->NewOutcome(); - for (int pl = 1; pl <= p_game->NumPlayers(); pl++) { - ov->SetPayoff(pl, Number(static_cast(subval[pl]))); +template +MixedBehaviorProfile BuildProfile(const Game &p_game, const SubgameSolution &p_solution) +{ + MixedBehaviorProfile profile(p_game); + for (const auto &player : p_game->GetPlayers()) { + for (const auto &infoset : player->GetInfosets()) { + for (const auto &action : infoset->GetActions()) { + profile[action] = p_solution.GetProfile().at(action); } - - values.push_back(ov); } - // printf("Finished with scenario %d of %d; total solutions so far = %d\n", - // soln, thissolns.Length(), solns.Length()); } - - n->DeleteTree(); + return profile; } template -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()); - for (int pl = 1; pl <= efg->NumPlayers(); pl++) { - for (int iset = 1; iset <= efg->GetPlayer(pl)->NumInfosets(); iset++) { - efg->GetPlayer(pl)->GetInfoset(iset)->SetLabel(lexical_cast(iset)); + int index = 1; + std::map infoset_map; + for (const auto &player : p_game->GetPlayers()) { + for (const auto &infoset : player->GetInfosets()) { + infoset_map[std::to_string(index++)] = infoset; } } - - List> vectors; - List values; - SolveSubgames(efg, DVector(efg->NumActions()), efg->GetRoot(), vectors, values); - - List> solutions; - for (int i = 1; i <= vectors.Length(); i++) { - solutions.push_back(MixedBehaviorProfile(p_game)); - for (int j = 1; j <= vectors[i].Length(); j++) { - solutions[i][j] = vectors[i][j]; + index = 1; + for (const auto &player : efg->GetPlayers()) { + for (const auto &infoset : player->GetInfosets()) { + infoset->SetLabel(std::to_string(index++)); } } - for (int i = 1; i <= solutions.Length(); i++) { - this->m_onEquilibrium->Render(solutions[i]); + + auto results = SolveSubgames(efg->GetRoot(), infoset_map, p_solver); + List> solutions; + for (const auto &result : results) { + solutions.push_back(BuildProfile(p_game, result)); + 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 0aae5462e..907cf5fe9 100644 --- a/src/games/nash.h +++ b/src/games/nash.h @@ -26,205 +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 class MixedStrategyNullRenderer : public MixedStrategyRenderer { -public: - ~MixedStrategyNullRenderer() override = default; - void Render(const MixedStrategyProfile &p_profile, - const std::string &p_label = "NE") const override - { - } -}; - -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) - { +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)); } - ~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; -}; + return ret; +} -template class BehavSolver { -public: - explicit BehavSolver(std::shared_ptr> p_onEquilibrium = nullptr); - virtual ~BehavSolver() = default; - - virtual List> Solve(const Game &) const = 0; +template +using BehaviorSolverType = std::function>(const Game &)>; -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 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: - void SolveSubgames(const Game &p_game, const DVector &p_templateSolution, const GameNode &n, - List> &solns, List &values) const; -}; +template +List> SolveBySubgames(const Game &, BehaviorSolverType p_solver, + BehaviorCallbackType p_onEquilibrium); // // Exception raised when maximum number of equilibria to compute @@ -237,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 cba8c0915..12a9a4a23 100644 --- a/src/games/stratmixed.h +++ b/src/games/stratmixed.h @@ -33,6 +33,8 @@ template class MixedStrategyProfileRep { public: Vector m_probs; StrategySupportProfile m_support; + /// The index into the strategy profile for a strategy (-1 if not in support) + std::map m_profileIndex; unsigned int m_gameversion; explicit MixedStrategyProfileRep(const StrategySupportProfile &); @@ -44,12 +46,17 @@ template class MixedStrategyProfileRep { /// Returns the probability the strategy is played const T &operator[](const GameStrategy &p_strategy) const { - return m_probs[m_support.m_profileIndex[p_strategy->GetId()]]; + return m_probs[m_profileIndex.at(p_strategy)]; } /// Returns the probability the strategy is played - T &operator[](const GameStrategy &p_strategy) + 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) { - return m_probs[m_support.m_profileIndex[p_strategy->GetId()]]; + 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; @@ -66,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 @@ -76,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 @@ -92,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) @@ -210,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 @@ -314,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); @@ -329,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 56f91f47d..c0f2b9a36 100644 --- a/src/games/stratpure.cc +++ b/src/games/stratpure.cc @@ -29,169 +29,67 @@ 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 (size_t 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; } //=========================================================================== -// class StrategyProfileIterator +// class StrategyContingencies //=========================================================================== -//--------------------------------------------------------------------------- -// Lifecycle -//--------------------------------------------------------------------------- - -StrategyProfileIterator::StrategyProfileIterator(const StrategySupportProfile &p_support) - : m_atEnd(false), m_support(p_support), m_currentStrat(m_support.GetGame()->NumPlayers()), - m_profile(m_support.GetGame()->NewPureStrategyProfile()), m_frozen1(0), m_frozen2(0) -{ - First(); -} - -StrategyProfileIterator::StrategyProfileIterator(const StrategySupportProfile &p_support, int pl, - int st) - : m_atEnd(false), m_support(p_support), m_currentStrat(m_support.GetGame()->NumPlayers()), - m_profile(m_support.GetGame()->NewPureStrategyProfile()), m_frozen1(pl), m_frozen2(0) -{ - m_currentStrat[pl] = st; - m_profile->SetStrategy(m_support.GetStrategy(pl, st)); - First(); -} - -StrategyProfileIterator::StrategyProfileIterator(const StrategySupportProfile &p_support, - const GameStrategy &p_strategy) - : m_atEnd(false), m_support(p_support), m_currentStrat(p_support.GetGame()->NumPlayers()), - m_profile(p_support.GetGame()->NewPureStrategyProfile()), - m_frozen1(p_strategy->GetPlayer()->GetNumber()), m_frozen2(0) -{ - m_currentStrat[m_frozen1] = p_strategy->GetNumber(); - m_profile->SetStrategy(p_strategy); - First(); -} - -StrategyProfileIterator::StrategyProfileIterator(const StrategySupportProfile &p_support, int pl1, - int st1, int pl2, int st2) - : m_atEnd(false), m_support(p_support), m_currentStrat(m_support.GetGame()->NumPlayers()), - m_profile(m_support.GetGame()->NewPureStrategyProfile()), m_frozen1(pl1), m_frozen2(pl2) +StrategyContingencies::StrategyContingencies(const StrategySupportProfile &p_support, + const std::vector &p_frozen) + : m_support(p_support), m_frozen(p_frozen) { - m_currentStrat[pl1] = st1; - m_profile->SetStrategy(m_support.GetStrategy(pl1, st1)); - m_currentStrat[pl2] = st2; - m_profile->SetStrategy(m_support.GetStrategy(pl2, st2)); - First(); + for (auto player : m_support.GetGame()->GetPlayers()) { + auto frozen = std::find_if(m_frozen.begin(), m_frozen.end(), [player](const GameStrategy &s) { + return s->GetPlayer() == player; + }); + if (frozen == m_frozen.end()) { + m_unfrozen.push_back(player); + } + } } -//--------------------------------------------------------------------------- -// Iteration -//--------------------------------------------------------------------------- - -void StrategyProfileIterator::First() +StrategyContingencies::iterator::iterator(StrategyContingencies *p_cont, bool p_end) + : m_cont(p_cont), m_atEnd(p_end), + m_profile(p_cont->m_support.GetGame()->NewPureStrategyProfile()) { - for (int pl = 1; pl <= m_support.GetGame()->NumPlayers(); pl++) { - if (pl == m_frozen1 || pl == m_frozen2) { - continue; - } - m_profile->SetStrategy(m_support.GetStrategy(pl, 1)); - m_currentStrat[pl] = 1; + for (auto strategy : m_cont->m_frozen) { + m_profile->SetStrategy(strategy); + } + for (auto player : m_cont->m_unfrozen) { + m_currentStrat[player] = m_cont->m_support.GetStrategies(player).begin(); + m_profile->SetStrategy(*m_currentStrat[player]); } } -void StrategyProfileIterator::operator++() +StrategyContingencies::iterator &StrategyContingencies::iterator::operator++() { - int pl = 1; - - while (true) { - if (pl == m_frozen1 || pl == m_frozen2) { - pl++; - if (pl > m_support.GetGame()->NumPlayers()) { - m_atEnd = true; - return; - } - continue; - } - - if (m_currentStrat[pl] < m_support.NumStrategies(pl)) { - m_profile->SetStrategy(m_support.GetStrategy(pl, ++(m_currentStrat[pl]))); - return; - } - m_profile->SetStrategy(m_support.GetStrategy(pl, 1)); - m_currentStrat[pl] = 1; - pl++; - if (pl > m_support.GetGame()->NumPlayers()) { - m_atEnd = true; - return; + for (auto player : m_cont->m_unfrozen) { + ++m_currentStrat[player]; + if (m_currentStrat[player] != m_cont->m_support.GetStrategies(player).end()) { + m_profile->SetStrategy(*m_currentStrat[player]); + return *this; } + m_currentStrat[player] = m_cont->m_support.GetStrategies(player).begin(); + m_profile->SetStrategy(*m_currentStrat[player]); } + m_atEnd = true; + return *this; } } // namespace Gambit diff --git a/src/games/stratpure.h b/src/games/stratpure.h index 70e27908d..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,75 +83,65 @@ 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; } }; -/// This class iterates through the contingencies in a strategic game. -/// It visits each strategy profile in turn, advancing one contingency -/// on each call of NextContingency(). Optionally, the strategy of -/// one player may be held fixed during the iteration (by the use of the -/// second constructor). -class StrategyProfileIterator { - friend class GameRep; - friend class GameTableRep; - +class StrategyContingencies { private: - bool m_atEnd; StrategySupportProfile m_support; - Array m_currentStrat; - PureStrategyProfile m_profile; - int m_frozen1, m_frozen2; - - /// Reset the iterator to the first contingency (this is called by ctors) - void First(); + std::vector m_unfrozen; + std::vector m_frozen; public: - /// @name Lifecycle - //@{ - /// Construct a new iterator on the support, with no strategies held fixed - explicit StrategyProfileIterator(const StrategySupportProfile &); - /// Construct a new iterator on the support, fixing player pl's strategy - StrategyProfileIterator(const StrategySupportProfile &s, int pl, int st); - /// Construct a new iterator on the support, fixing the given strategy - StrategyProfileIterator(const StrategySupportProfile &, const GameStrategy &); - /// Construct a new iterator on the support, fixing two players' strategies - StrategyProfileIterator(const StrategySupportProfile &s, int pl1, int st1, int pl2, int st2); - //@} + class iterator { + private: + StrategyContingencies *m_cont; + bool m_atEnd; + std::map m_currentStrat; + PureStrategyProfile m_profile; + + public: + iterator(StrategyContingencies *, bool p_end); + + iterator &operator++(); + + bool operator==(const iterator &p_other) const + { + if (m_atEnd && p_other.m_atEnd && m_cont == p_other.m_cont) { + return true; + } + if (m_atEnd != p_other.m_atEnd || m_cont != p_other.m_cont) { + return false; + } + return (m_profile.operator->() == p_other.m_profile.operator->()); + } + bool operator!=(const iterator &p_other) const { return !(*this == p_other); } - /// @name Iteration and data access - //@{ - /// Advance to the next contingency (prefix version) - void operator++(); - /// Advance to the next contingency (postfix version) - void operator++(int) { ++(*this); } - /// Has iterator gone past the end? - bool AtEnd() const { return m_atEnd; } - - /// Get the current strategy profile - PureStrategyProfile &operator*() { return m_profile; } - /// Get the current strategy profile - const PureStrategyProfile &operator*() const { return m_profile; } - //@} + PureStrategyProfile &operator*() { return m_profile; } + const PureStrategyProfile &operator*() const { return m_profile; } + }; + + explicit StrategyContingencies(const StrategySupportProfile &, + const std::vector &p_frozen = {}); + iterator begin() { return {this, false}; } + iterator end() { return {this, true}; } }; } // end namespace Gambit diff --git a/src/games/stratspt.cc b/src/games/stratspt.cc index ad13327b0..f4fff6f20 100644 --- a/src/games/stratspt.cc +++ b/src/games/stratspt.cc @@ -20,51 +20,42 @@ // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // +#include +#include + #include "gambit.h" #include "gametable.h" namespace Gambit { //=========================================================================== -// class StrategySupportProfile +// class StrategySupportProfile //=========================================================================== -//--------------------------------------------------------------------------- -// Lifecycle -//--------------------------------------------------------------------------- - -StrategySupportProfile::StrategySupportProfile(const Game &p_nfg) - : m_nfg(p_nfg), m_profileIndex(p_nfg->MixedProfileLength()) +StrategySupportProfile::StrategySupportProfile(const Game &p_nfg) : m_nfg(p_nfg) { - for (int pl = 1, index = 1; pl <= p_nfg->NumPlayers(); pl++) { - m_support.push_back(Array()); - for (int st = 1; st <= p_nfg->GetPlayer(pl)->NumStrategies(); st++, index++) { - m_support[pl].push_back(p_nfg->GetPlayer(pl)->GetStrategy(st)); - m_profileIndex[index] = index; + for (auto player : m_nfg->GetPlayers()) { + for (auto strategy : player->GetStrategies()) { + m_support[player].push_back(strategy); } } } -//--------------------------------------------------------------------------- -// General information -//--------------------------------------------------------------------------- - Array StrategySupportProfile::NumStrategies() const { - Array a(m_support.Length()); - - for (int pl = 1; pl <= a.Length(); pl++) { - a[pl] = m_support[pl].Length(); - } - return a; + Array dim(m_support.size()); + std::transform( + m_support.cbegin(), m_support.cend(), dim.begin(), + [](std::pair> a) { return a.second.size(); }); + return dim; } int StrategySupportProfile::MixedProfileLength() const { - int total = 0; - for (int pl = 1; pl <= m_nfg->NumPlayers(); total += m_support[pl++].Length()) - ; - return total; + return std::accumulate(m_support.cbegin(), m_support.cend(), 0, + [](size_t tot, std::pair> a) { + return tot + a.second.size(); + }); } template <> MixedStrategyProfile StrategySupportProfile::NewMixedStrategyProfile() const @@ -79,146 +70,69 @@ template <> MixedStrategyProfile StrategySupportProfile::NewMixedStrat bool StrategySupportProfile::IsSubsetOf(const StrategySupportProfile &p_support) const { - if (m_nfg != p_support.m_nfg) { - return false; - } - for (int pl = 1; pl <= m_support.Length(); pl++) { - if (m_support[pl].Length() > p_support.m_support[pl].Length()) { + for (auto player : m_nfg->GetPlayers()) { + if (!std::includes(p_support.m_support.at(player).begin(), + p_support.m_support.at(player).end(), m_support.at(player).begin(), + m_support.at(player).end(), + [](const GameStrategy &s, const GameStrategy &t) { + return s->GetNumber() < t->GetNumber(); + })) { return false; } - else { - for (int st = 1; st <= m_support[pl].Length(); st++) { - if (!p_support.m_support[pl].Contains(m_support[pl][st])) { - return false; - } - } - } } return true; } -namespace { - -std::string EscapeQuotes(const std::string &s) -{ - std::string ret; - - for (char c : s) { - if (c == '"') { - ret += '\\'; - } - ret += c; - } - - return ret; -} - -} // end anonymous namespace - void StrategySupportProfile::WriteNfgFile(std::ostream &p_file) const { - p_file << "NFG 1 R"; - p_file << " \"" << EscapeQuotes(m_nfg->GetTitle()) << "\" { "; - - for (auto player : m_nfg->GetPlayers()) { - p_file << '"' << EscapeQuotes(player->GetLabel()) << "\" "; - } - - p_file << "}\n\n{ "; - - for (auto player : m_nfg->GetPlayers()) { - p_file << "{ "; - for (auto strategy : GetStrategies(player)) { - p_file << '"' << EscapeQuotes(strategy->GetLabel()) << "\" "; - } - p_file << "}\n"; - } - - p_file << "}\n"; - - p_file << "\"" << EscapeQuotes(m_nfg->GetComment()) << "\"\n\n"; - - // For trees, we write the payoff version, since there need not be - // a one-to-one correspondence between outcomes and entries, when there - // are chance moves. - StrategyProfileIterator iter(*this); - - for (; !iter.AtEnd(); iter++) { - for (int pl = 1; pl <= m_nfg->NumPlayers(); pl++) { - p_file << (*iter)->GetPayoff(pl) << " "; - } - p_file << "\n"; + auto players = m_nfg->GetPlayers(); + p_file << "NFG 1 R " << std::quoted(m_nfg->GetTitle()) << ' ' + << FormatList(players, [](const GamePlayer &p) { return QuoteString(p->GetLabel()); }) + << std::endl + << std::endl; + p_file << "{ "; + for (auto player : players) { + p_file << FormatList(GetStrategies(player), [](const GameStrategy &s) { + return QuoteString(s->GetLabel()); + }) << std::endl; } - - p_file << '\n'; + p_file << "}" << std::endl; + p_file << std::quoted(m_nfg->GetComment()) << std::endl << std::endl; + + for (auto iter : StrategyContingencies(*this)) { + p_file << FormatList( + players, + [&iter](const GamePlayer &p) { + return lexical_cast(iter->GetPayoff(p)); + }, + false, false) + << std::endl; + }; } -//--------------------------------------------------------------------------- -// Modifying the support -//--------------------------------------------------------------------------- - void StrategySupportProfile::AddStrategy(const GameStrategy &p_strategy) { - // Get the null-pointer checking out of the way once and for all - GameStrategyRep *strategy = p_strategy; - Array &support = m_support[strategy->GetPlayer()->GetNumber()]; - - for (int i = 1; i <= support.Length(); i++) { - GameStrategyRep *s = support[i]; - if (s->GetNumber() == strategy->GetNumber()) { - // Strategy already in support; no change - return; - } - if (s->GetNumber() > strategy->GetNumber()) { - // Shift all higher-id strategies by one in the profile - m_profileIndex[strategy->GetId()] = m_profileIndex[s->GetId()]; - for (int id = s->GetId(); id <= m_profileIndex.Length(); id++) { - if (m_profileIndex[id] >= 0) { - m_profileIndex[id]++; - } - } - // Insert here - support.Insert(strategy, i); - return; - } - } - - // If we get here, p_strategy has a higher number than anything in the - // support for this player; append. - GameStrategyRep *last = support[support.Last()]; - m_profileIndex[strategy->GetId()] = m_profileIndex[last->GetId()] + 1; - for (int id = strategy->GetId() + 1; id <= m_profileIndex.Length(); id++) { - if (m_profileIndex[id] >= 0) { - m_profileIndex[id]++; - } + auto &support = m_support[p_strategy->GetPlayer()]; + auto pos = std::find_if(support.begin(), support.end(), [p_strategy](const GameStrategy &s) { + return s->GetNumber() >= p_strategy->GetNumber(); + }); + if (pos == support.end() || *pos != p_strategy) { + // Strategy is not in the support for the player; add at this location to keep sorted by number + support.insert(pos, p_strategy); } - support.push_back(strategy); } bool StrategySupportProfile::RemoveStrategy(const GameStrategy &p_strategy) { - GameStrategyRep *strategy = p_strategy; - Array &support = m_support[strategy->GetPlayer()->GetNumber()]; - - if (support.Length() == 1) { + auto &support = m_support[p_strategy->GetPlayer()]; + if (support.size() == 1) { return false; } - - for (int i = 1; i <= support.Length(); i++) { - GameStrategyRep *s = support[i]; - if (s == strategy) { - support.Remove(i); - m_profileIndex[strategy->GetId()] = -1; - // Shift strategies left in the profile - for (int id = strategy->GetId() + 1; id <= m_profileIndex.Length(); id++) { - if (m_profileIndex[id] >= 0) { - m_profileIndex[id]--; - } - } - return true; - } + auto pos = std::find(support.begin(), support.end(), p_strategy); + if (pos != support.end()) { + support.erase(pos); + return true; } - return false; } @@ -231,9 +145,9 @@ bool StrategySupportProfile::Dominates(const GameStrategy &s, const GameStrategy { bool equal = true; - for (StrategyProfileIterator iter(*this); !iter.AtEnd(); iter++) { - Rational ap = (*iter)->GetStrategyValue(s); - Rational bp = (*iter)->GetStrategyValue(t); + for (auto iter : StrategyContingencies(*this)) { + const Rational ap = iter->GetStrategyValue(s); + const Rational bp = iter->GetStrategyValue(t); if (p_strict && ap <= bp) { return false; } @@ -254,18 +168,16 @@ bool StrategySupportProfile::IsDominated(const GameStrategy &s, bool p_strict, bool p_external) const { if (p_external) { - GamePlayer player = s->GetPlayer(); - for (int st = 1; st <= player->NumStrategies(); st++) { - if (player->GetStrategy(st) != s && Dominates(player->GetStrategy(st), s, p_strict)) { + for (auto strategy : s->GetPlayer()->GetStrategies()) { + if (strategy != s && Dominates(strategy, s, p_strict)) { return true; } } return false; } else { - for (int i = 1; i <= NumStrategies(s->GetPlayer()->GetNumber()); i++) { - if (GetStrategy(s->GetPlayer()->GetNumber(), i) != s && - Dominates(GetStrategy(s->GetPlayer()->GetNumber(), i), s, p_strict)) { + for (auto strategy : GetStrategies(s->GetPlayer())) { + if (strategy != s && Dominates(strategy, s, p_strict)) { return true; } } @@ -273,96 +185,88 @@ bool StrategySupportProfile::IsDominated(const GameStrategy &s, bool p_strict, } } -bool StrategySupportProfile::Undominated(StrategySupportProfile &newS, int p_player, bool p_strict, - bool p_external) const -{ - Array set((p_external) ? m_nfg->GetPlayer(p_player)->NumStrategies() - : NumStrategies(p_player)); - - if (p_external) { - for (int st = 1; st <= set.Length(); st++) { - set[st] = m_nfg->GetPlayer(p_player)->GetStrategy(st); - } - } - else { - for (int st = 1; st <= set.Length(); st++) { - set[st] = GetStrategy(p_player, st); - } - } - - int min = 0, dis = set.Length() - 1; +namespace { +/// @brief Sort a range by a partial ordering and indicate minimal elements +/// +/// Sorts the range bracketed by `first` and `last` according to a partial ordering. +/// The partial ordering is specified by `greater`, which should be a binary +/// operation which returns `true` if the first argument is greater than the +/// second argument in the partial ordering. +/// +/// On termination, the range between `first` and `last` is sorted in decreasing +/// order by the partial ordering, in the sense that, if x > y according to the +/// partial ordering, then x will come before y in the sorted output. +/// +/// By this convention the set of **minimal elements** will all appear at the end +/// of the sorted output. The function returns an iterator pointing to the first +/// minimal element in the sorted range. +template +Iterator find_minimal_elements(Iterator first, Iterator last, Comparator greater) +{ + auto min = first, dis = std::prev(last); while (min <= dis) { - int pp; - for (pp = 0; pp < min && !Dominates(set[pp + 1], set[dis + 1], p_strict); pp++) - ; + auto pp = std::adjacent_find(first, last, greater); if (pp < min) { dis--; } else { - GameStrategy foo = set[dis + 1]; - set[dis + 1] = set[min + 1]; - set[min + 1] = foo; - - for (int inc = min + 1; inc <= dis;) { - if (Dominates(set[min + 1], set[dis + 1], p_strict)) { - // p_tracefile << GetStrategy(p_player, set[dis+1])->GetNumber() << " dominated by " << - // GetStrategy(p_player, set[min+1])->GetNumber() << '\n'; + std::iter_swap(dis, min); + auto inc = std::next(min); + while (inc <= dis) { + if (greater(*min, *dis)) { dis--; } - else if (Dominates(set[dis + 1], set[min + 1], p_strict)) { - // p_tracefile << GetStrategy(p_player, set[min+1])->GetNumber() << " dominated by " << - // GetStrategy(p_player, set[dis+1])->GetNumber() << '\n'; - foo = set[dis + 1]; - set[dis + 1] = set[min + 1]; - set[min + 1] = foo; + else if (greater(*dis, *min)) { + std::iter_swap(dis, min); dis--; } else { - foo = set[dis + 1]; - set[dis + 1] = set[inc + 1]; - set[inc + 1] = foo; + std::iter_swap(dis, inc); inc++; } } min++; } } + return min; +} - if (min + 1 <= set.Length()) { - for (int i = min + 1; i <= set.Length(); i++) { - newS.RemoveStrategy(set[i]); - } +} // end anonymous namespace - return true; +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->GetStrategies().size() + : p_support.GetStrategies(p_player).size()); + if (p_external) { + auto strategies = p_player->GetStrategies(); + std::copy(strategies.begin(), strategies.end(), set.begin()); } else { - return false; + auto strategies = p_support.GetStrategies(p_player); + std::copy(strategies.begin(), strategies.end(), set.begin()); } -} - -StrategySupportProfile StrategySupportProfile::Undominated(bool p_strict, bool p_external) const -{ - StrategySupportProfile newS(*this); - - for (int pl = 1; pl <= m_nfg->NumPlayers(); pl++) { - Undominated(newS, pl, p_strict, p_external); + auto min = + find_minimal_elements(set.begin(), set.end(), + [&p_support, p_strict](const GameStrategy &s, const GameStrategy &t) { + return p_support.Dominates(s, t, p_strict); + }); + + for (auto s = min; s != set.end(); s++) { + p_newSupport.RemoveStrategy(*s); } - - return newS; + return min != set.end(); } -StrategySupportProfile StrategySupportProfile::Undominated(bool p_strict, - const Array &players) const +StrategySupportProfile StrategySupportProfile::Undominated(bool p_strict, bool p_external) const { - StrategySupportProfile newS(*this); - - for (int i = 1; i <= players.Length(); i++) { - // tracefile << "Dominated strategies for player " << pl << ":\n"; - Undominated(newS, players[i], p_strict); + StrategySupportProfile newSupport(*this); + for (auto player : m_nfg->GetPlayers()) { + UndominatedForPlayer(*this, newSupport, player, p_strict, p_external); } - - return newS; + return newSupport; } //--------------------------------------------------------------------------- @@ -372,11 +276,12 @@ StrategySupportProfile StrategySupportProfile::Undominated(bool p_strict, bool StrategySupportProfile::Overwhelms(const GameStrategy &s, const GameStrategy &t, bool p_strict) const { - StrategyProfileIterator iter(*this); + auto cont = StrategyContingencies(*this); + auto iter = cont.begin(); Rational sMin = (*iter)->GetStrategyValue(s); Rational tMax = (*iter)->GetStrategyValue(t); - for (; !iter.AtEnd(); iter++) { + while (iter != cont.end()) { if ((*iter)->GetStrategyValue(s) < sMin) { sMin = (*iter)->GetStrategyValue(s); } @@ -393,40 +298,4 @@ bool StrategySupportProfile::Overwhelms(const GameStrategy &s, const GameStrateg return true; } -//=========================================================================== -// class StrategySupportProfile::iterator -//=========================================================================== - -bool StrategySupportProfile::iterator::GoToNext() -{ - if (strat != support.NumStrategies(pl)) { - strat++; - return true; - } - else if (pl != support.GetGame()->NumPlayers()) { - pl++; - strat = 1; - return true; - } - else { - return false; - } -} - -bool StrategySupportProfile::iterator::IsSubsequentTo(const GameStrategy &s) const -{ - if (pl > s->GetPlayer()->GetNumber()) { - return true; - } - else if (pl < s->GetPlayer()->GetNumber()) { - return false; - } - else if (strat > s->GetNumber()) { - return true; - } - else { - return false; - } -} - } // end namespace Gambit diff --git a/src/games/stratspt.h b/src/games/stratspt.h index f953abcef..7258d098d 100644 --- a/src/games/stratspt.h +++ b/src/games/stratspt.h @@ -27,9 +27,7 @@ namespace Gambit { -class StrategySupportProfile; - -/// \brief A support on a strategic game +/// @brief A support on a strategic game /// /// This class represents a subset of the strategies in strategic game. /// It is enforced that each player has at least one strategy; thus, @@ -41,22 +39,33 @@ class StrategySupportProfile; /// Within the support, strategies are maintained in the same order /// in which they appear in the underlying game. class StrategySupportProfile { - template friend class MixedStrategyProfile; - template friend class MixedStrategyProfileRep; - template friend class AGGMixedStrategyProfileRep; - template friend class BAGGMixedStrategyProfileRep; - protected: Game m_nfg; - Array> m_support; + std::map> m_support; - /// The index into a strategy profile for a strategy (-1 if not in support) - Array m_profileIndex; +public: + class Support { + private: + const StrategySupportProfile *m_profile; + GamePlayer m_player; - bool Undominated(StrategySupportProfile &newS, int p_player, bool p_strict, - bool p_external = false) const; + public: + using const_iterator = std::vector::const_iterator; + + Support() : m_profile(nullptr), m_player(nullptr) {} + Support(const StrategySupportProfile *p_profile, GamePlayer p_player) + : m_profile(p_profile), m_player(p_player) + { + } + + size_t size() const { return m_profile->m_support.at(m_player).size(); } + GameStrategy front() const { return m_profile->m_support.at(m_player).front(); } + GameStrategy back() const { return m_profile->m_support.at(m_player).back(); } + + const_iterator begin() const { return m_profile->m_support.at(m_player).begin(); } + const_iterator end() const { return m_profile->m_support.at(m_player).end(); } + }; -public: /// @name Lifecycle //@{ /// Constructor. By default, a support contains all strategies. @@ -82,9 +91,6 @@ class StrategySupportProfile { /// Returns the game on which the support is defined. Game GetGame() const { return m_nfg; } - /// Returns the number of strategies in the support for player pl. - int NumStrategies(int pl) const { return m_support[pl].size(); } - /// Returns the number of strategies in the support for all players. Array NumStrategies() const; @@ -93,21 +99,15 @@ class StrategySupportProfile { template MixedStrategyProfile NewMixedStrategyProfile() const; - /// Returns the strategy in the st'th position for player pl. - GameStrategy GetStrategy(int pl, int st) const { return m_support[pl][st]; } - /// 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 - const Array &GetStrategies(const GamePlayer &p_player) const - { - return m_support[p_player->GetNumber()]; - } + 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 m_profileIndex[s->GetId()] >= 0; } + bool Contains(const GameStrategy &s) const { return contains(m_support.at(s->GetPlayer()), s); } /// Returns true iff this support is a (weak) subset of the specified support bool IsSubsetOf(const StrategySupportProfile &) const; @@ -124,7 +124,7 @@ class StrategySupportProfile { /// Add a strategy to the support. void AddStrategy(const GameStrategy &); - /// \brief Removes a strategy from the support + /// @brief Removes a strategy from the support /// /// Removes a strategy from the support. If the strategy is /// not present, or if the strategy is the only strategy for that @@ -138,65 +138,14 @@ class StrategySupportProfile { bool Dominates(const GameStrategy &s, const GameStrategy &t, bool p_strict) const; bool IsDominated(const GameStrategy &s, bool p_strict, bool p_external = false) const; - /// Returns a copy of the support with dominated strategies eliminated + /// Returns a copy of the support with dominated strategies removed StrategySupportProfile Undominated(bool p_strict, bool p_external = false) const; - StrategySupportProfile Undominated(bool strong, const Array &players) const; //@} /// @name Identification of overwhelmed strategies //@{ bool Overwhelms(const GameStrategy &s, const GameStrategy &t, bool p_strict) const; //@} - - class iterator { - public: - /// @name Lifecycle - //@{ - explicit iterator(const StrategySupportProfile &S, int p_pl = 1, int p_st = 1) - : support(S), pl(p_pl), strat(p_st) - { - } - ~iterator() = default; - //@} - - /// @name Operator overloading - //@{ - bool operator==(const iterator &other) const - { - return (support == other.support && pl == other.pl && strat == other.strat); - } - bool operator!=(const iterator &other) const { return !(*this == other); } - //@} - - /// @name Manipulation - //@{ - /// Advance to next strategy; return False when advancing past last strategy. - bool GoToNext(); - /// Advance to next strategy - void operator++() { GoToNext(); } - //@} - - /// @name Access to state information - //@{ - GameStrategy GetStrategy() const { return support.GetStrategy(pl, strat); } - int StrategyIndex() const { return strat; } - GamePlayer GetPlayer() const { return support.GetGame()->GetPlayer(pl); } - int PlayerIndex() const { return pl; } - - bool IsLast() const - { - return (pl == support.GetGame()->NumPlayers() && strat == support.NumStrategies(pl)); - } - bool IsSubsequentTo(const GameStrategy &) const; - //@} - - private: - const StrategySupportProfile &support; - int pl, strat; - }; - - iterator begin() const { return iterator(*this); } - iterator end() const { return iterator(*this, m_nfg->NumPlayers() + 1); } }; } // end namespace Gambit diff --git a/src/games/writer.cc b/src/games/writer.cc index 299895091..a3c4c2fe4 100644 --- a/src/games/writer.cc +++ b/src/games/writer.cc @@ -23,26 +23,27 @@ #include "gambit.h" #include "writer.h" -using namespace Gambit; +namespace Gambit { -std::string HTMLGameWriter::Write(const Game &p_game, int p_rowPlayer, int p_colPlayer) const +std::string WriteHTMLFile(const Game &p_game, const GamePlayer &p_rowPlayer, + const GamePlayer &p_colPlayer) { std::string theHtml; theHtml += "

" + p_game->GetTitle() + "

\n"; - for (StrategyProfileIterator iter(p_game, p_rowPlayer, 1, p_colPlayer, 1); !iter.AtEnd(); - iter++) { + for (auto iter : StrategyContingencies( + p_game, {p_rowPlayer->GetStrategies().front(), p_colPlayer->GetStrategies().front()})) { if (p_game->NumPlayers() > 2) { theHtml += "
Subtable with strategies:
"; - for (int pl = 1; pl <= p_game->NumPlayers(); pl++) { - if (pl == p_rowPlayer || pl == p_colPlayer) { + for (auto player : p_game->GetPlayers()) { + if (player == p_rowPlayer || player == p_colPlayer) { continue; } theHtml += "
Player "; - theHtml += lexical_cast(pl); + theHtml += std::to_string(player->GetNumber()); theHtml += " Strategy "; - theHtml += lexical_cast((*iter)->GetStrategy(pl)->GetNumber()); + theHtml += std::to_string(iter->GetStrategy(player)->GetNumber()); theHtml += "
"; } } @@ -50,35 +51,35 @@ std::string HTMLGameWriter::Write(const Game &p_game, int p_rowPlayer, int p_col theHtml += ""; theHtml += ""; theHtml += ""; - for (int st = 1; st <= p_game->GetPlayer(p_colPlayer)->NumStrategies(); st++) { + for (const auto &strategy : p_colPlayer->GetStrategies()) { theHtml += ""; } theHtml += ""; - for (int st1 = 1; st1 <= p_game->GetPlayer(p_rowPlayer)->NumStrategies(); st1++) { - PureStrategyProfile profile = *iter; - profile->SetStrategy(p_game->GetPlayer(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_game->GetPlayer(p_colPlayer)->NumStrategies(); st2++) { - profile->SetStrategy(p_game->GetPlayer(p_colPlayer)->GetStrategy(st2)); + for (const auto &col_strategy : p_colPlayer->GetStrategies()) { + profile->SetStrategy(col_strategy); theHtml += "
"; - theHtml += p_game->GetPlayer(p_colPlayer)->GetStrategy(st)->GetLabel(); + theHtml += strategy->GetLabel(); theHtml += "
"; - theHtml += p_game->GetPlayer(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 += ","; } } @@ -93,33 +94,34 @@ std::string HTMLGameWriter::Write(const Game &p_game, int p_rowPlayer, int p_col return theHtml; } -std::string LaTeXGameWriter::Write(const Game &p_game, int p_rowPlayer, int p_colPlayer) const +std::string WriteLaTeXFile(const Game &p_game, const GamePlayer &p_rowPlayer, + const GamePlayer &p_colPlayer) { std::string theHtml; - for (StrategyProfileIterator iter(p_game, p_rowPlayer, 1, p_colPlayer, 1); !iter.AtEnd(); - iter++) { + for (auto iter : StrategyContingencies( + p_game, {p_rowPlayer->GetStrategies().front(), p_colPlayer->GetStrategies().front()})) { theHtml += "\\begin{game}{"; - theHtml += lexical_cast(p_game->GetPlayer(p_rowPlayer)->NumStrategies()); + theHtml += std::to_string(p_rowPlayer->GetStrategies().size()); theHtml += "}{"; - theHtml += lexical_cast(p_game->GetPlayer(p_colPlayer)->NumStrategies()); + theHtml += std::to_string(p_colPlayer->GetStrategies().size()); theHtml += "}["; - theHtml += p_game->GetPlayer(p_rowPlayer)->GetLabel(); + theHtml += p_rowPlayer->GetLabel(); theHtml += "]["; - theHtml += p_game->GetPlayer(p_colPlayer)->GetLabel(); + theHtml += p_colPlayer->GetLabel(); theHtml += "]"; if (p_game->NumPlayers() > 2) { theHtml += "["; - for (int pl = 1; pl <= p_game->NumPlayers(); pl++) { - if (pl == p_rowPlayer || pl == p_colPlayer) { + for (auto player : p_game->GetPlayers()) { + if (player == p_rowPlayer || player == p_colPlayer) { continue; } theHtml += "Player "; - theHtml += lexical_cast(pl); + theHtml += lexical_cast(player->GetNumber()); theHtml += " Strategy "; - theHtml += lexical_cast((*iter)->GetStrategy(pl)->GetNumber()); + theHtml += lexical_cast(iter->GetStrategy(player)->GetNumber()); theHtml += " "; } theHtml += "]"; @@ -127,44 +129,44 @@ std::string LaTeXGameWriter::Write(const Game &p_game, int p_rowPlayer, int p_co theHtml += "\n&"; - for (int st = 1; st <= p_game->GetPlayer(p_colPlayer)->NumStrategies(); st++) { - theHtml += p_game->GetPlayer(p_colPlayer)->GetStrategy(st)->GetLabel(); - if (st < p_game->GetPlayer(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_game->GetPlayer(p_rowPlayer)->NumStrategies(); st1++) { - PureStrategyProfile profile = *iter; - profile->SetStrategy(p_game->GetPlayer(p_rowPlayer)->GetStrategy(st1)); - theHtml += p_game->GetPlayer(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_game->GetPlayer(p_colPlayer)->NumStrategies(); st2++) { - profile->SetStrategy(p_game->GetPlayer(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_game->GetPlayer(p_colPlayer)->NumStrategies()) { + if (col_strategy != p_colPlayer->GetStrategies().back()) { theHtml += " & "; } } - if (st1 < p_game->GetPlayer(p_rowPlayer)->NumStrategies()) { + if (row_strategy != p_rowPlayer->GetStrategies().back()) { theHtml += "\\\\\n"; } } @@ -174,3 +176,5 @@ std::string LaTeXGameWriter::Write(const Game &p_game, int p_rowPlayer, int p_co theHtml += "\n"; return theHtml; } + +} // end namespace Gambit diff --git a/src/games/writer.h b/src/games/writer.h index 16b864371..003059eec 100644 --- a/src/games/writer.h +++ b/src/games/writer.h @@ -27,6 +27,46 @@ namespace Gambit { +/// @brief Render text as an explicit double-quoted string, escaping any double-quotes +/// in the text with a backslash +inline std::string QuoteString(const std::string &s) +{ + std::ostringstream ss; + ss << std::quoted(s); + return ss.str(); +} + +/// @brief Render a list of objects as a space-separated list of elements, delimited +/// by curly braces on either side +/// @param[in] p_container The container over which to iterate +/// @param[in] p_renderer A callable which returns a string representing each item +/// @param[in] p_commas Use comma as delimiter between items (default is no) +/// @param[in] p_braces Whether to include curly braces on either side of the list +/// @returns The formatted list as a string following the conventions of Gambit savefiles. +template +std::string FormatList(const C &p_container, T p_renderer, bool p_commas = false, + bool p_braces = true) +{ + std::string s, delim; + if (p_braces) { + s = "{"; + delim = " "; + } + for (auto element : p_container) { + s += delim + p_renderer(element); + if (p_commas) { + delim = ", "; + } + else { + delim = " "; + } + } + if (p_braces) { + s += " }"; + } + return s; +} + /// /// Abstract base class for objects that write games to various formats /// @@ -39,38 +79,16 @@ class GameWriter { }; /// -/// Format the strategic representation of a game to HTML tables. +/// Convert the game to HTML, selecting the row and column player. /// -class HTMLGameWriter : public GameWriter { -public: - /// - /// Convert the game to HTML, with player 1 on the rows and player 2 - /// on the columns. - /// - std::string Write(const Game &p_game) const override { return Write(p_game, 1, 2); } - - /// - /// Convert the game to HTML, selecting the row and column player numbers. - /// - std::string Write(const Game &p_game, int p_rowPlayer, int p_colPlayer) const; -}; +std::string WriteHTMLFile(const Game &p_game, const GamePlayer &p_rowPlayer, + const GamePlayer &p_colPlayer); /// /// Format the strategic representation of a game to LaTeX sgame style /// -class LaTeXGameWriter : public GameWriter { -public: - /// - /// Convert the game to LaTeX, with player 1 on the rows and player 2 - /// on the columns. - /// - std::string Write(const Game &p_game) const override { return Write(p_game, 1, 2); } - - /// - /// Convert the game to LaTeX, selecting the row and column player numbers. - /// - std::string Write(const Game &p_game, int p_rowPlayer, int p_colPlayer) const; -}; +std::string WriteLaTeXFile(const Game &p_game, const GamePlayer &p_rowPlayer, + const GamePlayer &p_colPlayer); } // end namespace Gambit diff --git a/src/gui/analysis.cc b/src/gui/analysis.cc index 0fe2c6f03..6d86a7b3c 100644 --- a/src/gui/analysis.cc +++ b/src/gui/analysis.cc @@ -59,7 +59,7 @@ MixedStrategyProfile OutputToMixedProfile(gbtGameDocument *p_doc, const wxStr if (tok.GetNextToken() == wxT("NE")) { if (tok.CountTokens() == (unsigned int)profile.MixedProfileLength()) { - for (int i = 1; i <= profile.MixedProfileLength(); i++) { + for (size_t i = 1; i <= profile.MixedProfileLength(); i++) { profile[i] = lexical_cast(std::string((const char *)tok.GetNextToken().mb_str())); } @@ -79,7 +79,7 @@ MixedBehaviorProfile OutputToBehavProfile(gbtGameDocument *p_doc, const wxStr if (tok.GetNextToken() == wxT("NE")) { if (tok.CountTokens() == (unsigned int)profile.BehaviorProfileLength()) { - for (int i = 1; i <= profile.BehaviorProfileLength(); i++) { + for (size_t i = 1; i <= profile.BehaviorProfileLength(); i++) { profile[i] = lexical_cast(std::string((const char *)tok.GetNextToken().mb_str())); } @@ -101,7 +101,7 @@ template void gbtAnalysisProfileList::AddOutput(const wxString &p_o m_behavProfiles.push_back(profile); m_mixedProfiles.push_back( std::make_shared>(profile->ToMixedProfile())); - m_current = m_behavProfiles.Length(); + m_current = m_behavProfiles.size(); } else { auto profile = @@ -110,7 +110,7 @@ template void gbtAnalysisProfileList::AddOutput(const wxString &p_o if (m_doc->IsTree()) { m_behavProfiles.push_back(std::make_shared>(*profile)); } - m_current = m_mixedProfiles.Length(); + m_current = m_mixedProfiles.size(); } } catch (gbtNotNashException &) { @@ -128,10 +128,10 @@ template void gbtAnalysisProfileList::BuildNfg() template int gbtAnalysisProfileList::NumProfiles() const { if (m_doc->IsTree()) { - return m_behavProfiles.Length(); + return m_behavProfiles.size(); } else { - return m_mixedProfiles.Length(); + return m_mixedProfiles.size(); } } @@ -156,7 +156,7 @@ MixedStrategyProfile TextToMixedProfile(gbtGameDocument *p_doc, const wxStrin wxStringTokenizer tok(p_text, wxT(",")); - for (int i = 1; i <= profile.MixedProfileLength(); i++) { + for (size_t i = 1; i <= profile.MixedProfileLength(); i++) { profile[i] = lexical_cast(std::string((const char *)tok.GetNextToken().mb_str())); } @@ -169,7 +169,7 @@ MixedBehaviorProfile TextToBehavProfile(gbtGameDocument *p_doc, const wxStrin MixedBehaviorProfile profile(p_doc->GetGame()); wxStringTokenizer tok(p_text, wxT(",")); - for (int i = 1; i <= profile.BehaviorProfileLength(); i++) { + for (size_t i = 1; i <= profile.BehaviorProfileLength(); i++) { profile[i] = lexical_cast(std::string((const char *)tok.GetNextToken().mb_str())); } @@ -195,25 +195,25 @@ 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.Length(); + 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; - m_current = m_mixedProfiles.Length(); + m_current = m_mixedProfiles.size(); } } } 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 &) { @@ -441,7 +441,7 @@ template void gbtAnalysisProfileList::Save(std::ostream &p_file) co for (int j = 1; j <= NumProfiles(); j++) { const MixedBehaviorProfile &behav = *m_behavProfiles[j]; p_file << "\n"; - for (int k = 1; k <= behav.BehaviorProfileLength(); k++) { + for (size_t k = 1; k <= behav.BehaviorProfileLength(); k++) { p_file << behav[k]; if (k < behav.BehaviorProfileLength()) { p_file << ","; @@ -457,7 +457,7 @@ template void gbtAnalysisProfileList::Save(std::ostream &p_file) co for (int j = 1; j <= NumProfiles(); j++) { const MixedStrategyProfile &mixed = *m_mixedProfiles[j]; p_file << "\n"; - for (int k = 1; k <= mixed.MixedProfileLength(); k++) { + for (size_t k = 1; k <= mixed.MixedProfileLength(); k++) { p_file << mixed[k]; if (k < mixed.MixedProfileLength()) { p_file << ","; diff --git a/src/gui/app.cc b/src/gui/app.cc index ff9211c19..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."), @@ -70,11 +70,11 @@ bool gbtApplication::OnInit() } } - if (m_documents.Length() == 0) { + if (m_documents.size() == 0) { // 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.Length(); 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 d33224192..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 @@ -68,11 +68,14 @@ class gbtApplication : public wxApp { //! //@{ void AddDocument(gbtGameDocument *p_doc) { m_documents.push_back(p_doc); } - void RemoveDocument(gbtGameDocument *p_doc) { m_documents.Remove(m_documents.Find(p_doc)); } + void RemoveDocument(gbtGameDocument *p_doc) + { + m_documents.erase(std::find(m_documents.begin(), m_documents.end(), p_doc)); + } bool AreDocumentsModified() const; //@} }; 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 dbf52580d..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,17 +49,16 @@ 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()) { - selection = m_infosetList.Length(); + selection = m_infosetList.size(); } } } @@ -65,15 +66,14 @@ 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.Length(); + 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/dlefglayout.cc b/src/gui/dlefglayout.cc index de7779c4e..514087d8f 100644 --- a/src/gui/dlefglayout.cc +++ b/src/gui/dlefglayout.cc @@ -165,19 +165,19 @@ gbtLayoutBranchesPanel::gbtLayoutBranchesPanel(wxWindow *p_parent, const gbtStyl styleBoxSizer->Add(styleSizer, 1, wxALL | wxEXPAND, 5); topSizer->Add(styleBoxSizer, 0, wxALL | wxALIGN_CENTER, 5); - auto *lengthSizer = new wxStaticBoxSizer(wxHORIZONTAL, this, _T("Length of branches")); + auto *lengthSizer = new wxStaticBoxSizer(wxHORIZONTAL, this, _T("size of branches")); auto *gridSizer = new wxFlexGridSizer(2); gridSizer->AddGrowableCol(1); - gridSizer->Add(new wxStaticText(this, wxID_ANY, _("Length of branch fork")), 0, + gridSizer->Add(new wxStaticText(this, wxID_ANY, _("size of branch fork")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); m_branchLength = new wxSpinCtrl( this, wxID_ANY, wxString::Format(_T("%d"), p_settings.BranchLength()), wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, BRANCH_LENGTH_MIN, BRANCH_LENGTH_MAX); gridSizer->Add(m_branchLength, 1, wxALL | wxEXPAND, 5); - gridSizer->Add(new wxStaticText(this, wxID_ANY, _("Length of branch tine")), 1, + gridSizer->Add(new wxStaticText(this, wxID_ANY, _("size of branch tine")), 1, wxALIGN_CENTER_VERTICAL | wxALL, 5); m_tineLength = new wxSpinCtrl( this, wxID_ANY, wxString::Format(_T("%d"), p_settings.TineLength()), wxDefaultPosition, diff --git a/src/gui/dlefglogit.cc b/src/gui/dlefglogit.cc index 8b35d49a3..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) { @@ -178,14 +178,14 @@ void gbtLogitBehavList::AddProfile(const wxString &p_text, bool p_forceShow) m_lambdas.push_back((double)Gambit::lexical_cast( std::string((const char *)tok.GetNextToken().mb_str()))); - for (int i = 1; i <= profile->BehaviorProfileLength(); i++) { + for (size_t i = 1; i <= profile->BehaviorProfileLength(); i++) { (*profile)[i] = Gambit::lexical_cast( std::string((const char *)tok.GetNextToken().mb_str())); } m_profiles.push_back(profile); - if (p_forceShow || m_profiles.Length() - GetNumberRows() > 20) { - AppendRows(m_profiles.Length() - GetNumberRows()); + if (p_forceShow || m_profiles.size() - GetNumberRows() > 20) { + AppendRows(m_profiles.size() - GetNumberRows()); MakeCellVisible(wxSheetCoords(GetNumberRows() - 1, 0)); } @@ -207,8 +207,8 @@ END_EVENT_TABLE() gbtLogitBehavDialog::gbtLogitBehavDialog(wxWindow *p_parent, gbtGameDocument *p_doc) : wxDialog(p_parent, wxID_ANY, wxT("Compute quantal response equilibria"), wxDefaultPosition), - m_doc(p_doc), m_process(nullptr), m_timer(this, GBT_ID_TIMER), - m_behavList(new gbtLogitBehavList(this, m_doc)) + m_doc(p_doc), m_process(nullptr), m_behavList(new gbtLogitBehavList(this, m_doc)), + m_timer(this, GBT_ID_TIMER) { auto *sizer = new wxBoxSizer(wxVERTICAL); @@ -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 417c5d63d..ed9dc08fe 100644 --- a/src/gui/dlnash.cc +++ b/src/gui/dlnash.cc @@ -33,7 +33,7 @@ static wxString s_recommended(wxT("with Gambit's recommended method")); static wxString s_enumpure(wxT("by looking for pure strategy equilibria")); static wxString s_enummixed(wxT("by enumerating extreme points")); -// static wxString s_enumpoly(wxT("by solving systems of polynomial equations")); +static wxString s_enumpoly(wxT("by solving systems of polynomial equations")); static wxString s_gnm(wxT("by global Newton tracing")); static wxString s_ipa(wxT("by iterated polymatrix approximation")); static wxString s_lp(wxT("by solving a linear program")); @@ -67,14 +67,14 @@ gbtNashChoiceDialog::gbtNashChoiceDialog(wxWindow *p_parent, gbtGameDocument *p_ topSizer->Add(m_countChoice, 0, wxALL | wxEXPAND, 5); if (p_doc->NumPlayers() == 2 && m_doc->IsConstSum()) { - wxString methodChoices[] = {s_recommended, s_lp, s_simpdiv, s_logit}; + wxString methodChoices[] = {s_recommended, s_lp, s_simpdiv, s_logit, s_enumpoly}; m_methodChoice = - new wxChoice(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, 4, methodChoices); + new wxChoice(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, 5, methodChoices); } else { - wxString methodChoices[] = {s_recommended, s_simpdiv, s_logit}; + wxString methodChoices[] = {s_recommended, s_simpdiv, s_logit, s_enumpoly}; m_methodChoice = - new wxChoice(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, 3, methodChoices); + new wxChoice(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, 4, methodChoices); } m_methodChoice->SetSelection(0); topSizer->Add(m_methodChoice, 0, wxALL | wxEXPAND, 5); @@ -128,7 +128,7 @@ void gbtNashChoiceDialog::OnCount(wxCommandEvent &p_event) m_methodChoice->Append(s_liap); m_methodChoice->Append(s_gnm); m_methodChoice->Append(s_ipa); - // m_methodChoice->Append(s_enumpoly); + m_methodChoice->Append(s_enumpoly); } else { if (m_doc->NumPlayers() == 2) { @@ -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,98 +195,92 @@ 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 ") wxT("complementarity program ") + game); + 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 ") - wxT("strategies in strategic game")); + 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 ") wxT("systems ") + game); + 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 ") - wxT("in strategic game")); + 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); + 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 ") wxT("in strategic game")); + 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 ") - wxT("in strategic game")); + 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 ") wxT("program ") + - game); + 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 ") wxT("in strategic game")); + cmd->SetDescription(count + wxT(" by simplicial subdivision in strategic game")); } else { // Shouldn't happen! 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 c5063b28a..102fd7fc3 100644 --- a/src/gui/dlnfglogit.cc +++ b/src/gui/dlnfglogit.cc @@ -61,7 +61,7 @@ class LogitMixedBranch { void AddProfile(const wxString &p_text); - int NumPoints() const { return m_lambdas.Length(); } + int NumPoints() const { return m_lambdas.size(); } double GetLambda(int p_index) const { return m_lambdas[p_index]; } const Array &GetLambdas() const { return m_lambdas; } const MixedStrategyProfile &GetProfile(int p_index) { return *m_profiles[p_index]; } @@ -77,7 +77,7 @@ void LogitMixedBranch::AddProfile(const wxString &p_text) m_lambdas.push_back( (double)lexical_cast(std::string((const char *)tok.GetNextToken().mb_str()))); - for (int i = 1; i <= profile->MixedProfileLength(); i++) { + for (size_t i = 1; i <= profile->MixedProfileLength(); i++) { (*profile)[i] = lexical_cast(std::string((const char *)tok.GetNextToken().mb_str())); } @@ -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); @@ -456,8 +455,8 @@ class LogitPlotPanel : public wxPanel { LogitPlotPanel::LogitPlotPanel(wxWindow *p_parent, gbtGameDocument *p_doc) : wxPanel(p_parent, wxID_ANY), m_doc(p_doc), m_branch(p_doc), - m_plotCtrl(new gbtLogitPlotCtrl(this, p_doc)), - m_plotStrategies(new gbtLogitPlotStrategyList(this, p_doc)) + m_plotStrategies(new gbtLogitPlotStrategyList(this, p_doc)), + m_plotCtrl(new gbtLogitPlotCtrl(this, p_doc)) { m_plotCtrl->SetSizeHints(wxSize(600, 400)); @@ -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; } @@ -605,7 +604,7 @@ END_EVENT_TABLE() LogitMixedDialog::LogitMixedDialog(wxWindow *p_parent, gbtGameDocument *p_doc) : wxDialog(p_parent, wxID_ANY, wxT("Compute quantal response equilibria"), wxDefaultPosition), - m_doc(p_doc), m_timer(this, GBT_ID_TIMER), m_plot(new LogitPlotPanel(this, m_doc)) + m_doc(p_doc), m_plot(new LogitPlotPanel(this, m_doc)), m_timer(this, GBT_ID_TIMER) { auto *sizer = new wxBoxSizer(wxVERTICAL); @@ -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 bdaa01176..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,50 +325,49 @@ 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 (int i = 1; i <= m_nodeList.Length(); i++) { - if (m_nodeList[i]->NodeHitTest(p_x, p_y)) { - return m_nodeList[i]->GetNode(); + for (const auto &entry : m_nodeList) { + if (entry->NodeHitTest(p_x, p_y)) { + return entry->GetNode(); } } return nullptr; } -Gambit::GameNode gbtTreeLayout::OutcomeHitTest(int p_x, int p_y) const +GameNode gbtTreeLayout::OutcomeHitTest(int p_x, int p_y) const { - for (int i = 1; i <= m_nodeList.Length(); i++) { - if (m_nodeList[i]->OutcomeHitTest(p_x, p_y)) { - return m_nodeList[i]->GetNode(); + for (const auto &entry : m_nodeList) { + if (entry->OutcomeHitTest(p_x, p_y)) { + return entry->GetNode(); } } return nullptr; } -Gambit::GameNode gbtTreeLayout::BranchAboveHitTest(int p_x, int p_y) const +GameNode gbtTreeLayout::BranchAboveHitTest(int p_x, int p_y) const { - for (int i = 1; i <= m_nodeList.Length(); i++) { - if (m_nodeList[i]->BranchAboveHitTest(p_x, p_y)) { - return m_nodeList[i]->GetNode()->GetParent(); + for (const auto &entry : m_nodeList) { + if (entry->BranchAboveHitTest(p_x, p_y)) { + return entry->GetNode()->GetParent(); } } return nullptr; } -Gambit::GameNode gbtTreeLayout::BranchBelowHitTest(int p_x, int p_y) const +GameNode gbtTreeLayout::BranchBelowHitTest(int p_x, int p_y) const { - for (int i = 1; i <= m_nodeList.Length(); i++) { - if (m_nodeList[i]->BranchAboveHitTest(p_x, p_y)) { - return m_nodeList[i]->GetNode()->GetParent(); + for (const auto &entry : m_nodeList) { + if (entry->BranchAboveHitTest(p_x, p_y)) { + return entry->GetNode()->GetParent(); } } return nullptr; } -Gambit::GameNode gbtTreeLayout::InfosetHitTest(int p_x, int p_y) const +GameNode gbtTreeLayout::InfosetHitTest(int p_x, int p_y) const { - for (int i = 1; i <= m_nodeList.Length(); i++) { - gbtNodeEntry *entry = m_nodeList[i]; + for (const auto &entry : m_nodeList) { if (entry->GetNextMember() && entry->GetNode()->GetInfoset()) { if (p_x > entry->X() + entry->GetSublevel() * m_infosetSpacing - 2 && p_x < entry->X() + entry->GetSublevel() * m_infosetSpacing + 2) { @@ -386,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) { @@ -444,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) { @@ -456,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}; } @@ -484,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) { @@ -495,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; } @@ -512,45 +514,47 @@ 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 (int i = 1; i <= m_nodeList.Length(); i++) { - if (m_nodeList[i]->GetNode() == p_node) { - return m_nodeList[i]; + for (const auto &entry : m_nodeList) { + if (entry->GetNode() == p_node) { + return entry; } } 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) { - for (int i = m_nodeList.Find(entry) - 1; i >= 1; i--) { - if (m_nodeList[i]->GetLevel() == entry->GetLevel()) { - return m_nodeList[i]->GetNode(); + auto e = std::next(std::find(m_nodeList.rbegin(), m_nodeList.rend(), entry)); + while (e != m_nodeList.rend()) { + if ((*e)->GetLevel() == entry->GetLevel()) { + return (*e)->GetNode(); } } } 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) { - for (int i = m_nodeList.Find(entry) + 1; i <= m_nodeList.Length(); i++) { - if (m_nodeList[i]->GetLevel() == entry->GetLevel()) { - return m_nodeList[i]->GetNode(); + auto e = std::next(std::find(m_nodeList.begin(), m_nodeList.end(), entry)); + while (e != m_nodeList.end()) { + if ((*e)->GetLevel() == entry->GetLevel()) { + return (*e)->GetNode(); } + ++e; } } 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(); @@ -559,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) { @@ -569,16 +573,17 @@ 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))) { - m_nodeList[p_node->GetChild(i)->GetNumber()]->SetInSupport(false); + 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(action); + }))->SetInSupport(false); } } entry->SetY((y1 + yn) / 2); @@ -632,8 +637,9 @@ gbtNodeEntry *gbtTreeLayout::NextInfoset(gbtNodeEntry *e) { const gbtStyle &draw_settings = m_doc->GetStyle(); - for (int pos = m_nodeList.Find(e) + 1; pos <= m_nodeList.Length(); pos++) { - gbtNodeEntry *e1 = m_nodeList[pos]; + auto entry = std::next(std::find(m_nodeList.begin(), m_nodeList.end(), e)); + while (entry != m_nodeList.end()) { + gbtNodeEntry *e1 = *entry; // infosets are the same and the nodes are on the same level if (e->GetNode()->GetInfoset() == e1->GetNode()->GetInfoset()) { if (draw_settings.InfosetConnect() == GBT_INFOSET_CONNECT_ALL) { @@ -643,6 +649,7 @@ gbtNodeEntry *gbtTreeLayout::NextInfoset(gbtNodeEntry *e) return e1; } } + ++entry; } return nullptr; } @@ -655,15 +662,13 @@ gbtNodeEntry *gbtTreeLayout::NextInfoset(gbtNodeEntry *e) // void gbtTreeLayout::CheckInfosetEntry(gbtNodeEntry *e) { - int pos; - gbtNodeEntry *infoset_entry, *e1; + gbtNodeEntry *infoset_entry; // Check if the infoset this entry belongs to (on this level) has already // been processed. If so, make this entry->num the same as the one already // processed and return infoset_entry = NextInfoset(e); - for (pos = 1; pos <= m_nodeList.Length(); pos++) { - e1 = m_nodeList[pos]; - // if the infosets are the same and they are on the same level and e1 has been processed + 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 (e->GetNode()->GetInfoset() == e1->GetNode()->GetInfoset() && e->GetLevel() == e1->GetLevel() && e1->GetSublevel() > 0) { e->SetSublevel(e1->GetSublevel()); @@ -684,8 +689,7 @@ void gbtTreeLayout::CheckInfosetEntry(gbtNodeEntry *e) // find the entry on the same level with the maximum num. // This entry will have num = num+1. int num = 0; - for (pos = 1; pos <= m_nodeList.Length(); pos++) { - e1 = m_nodeList[pos]; + for (const auto &e1 : m_nodeList) { // Find the max num for this level if (e->GetLevel() == e1->GetLevel()) { num = std::max(e1->GetSublevel(), num); @@ -696,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); } } } @@ -723,13 +722,12 @@ 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) ; // find the max e->num for each level - for (int pos = 1; pos <= m_nodeList.Length(); pos++) { - gbtNodeEntry *entry = m_nodeList[pos]; + for (const auto &entry : m_nodeList) { nums[entry->GetLevel()] = std::max(entry->GetSublevel() + 1, nums[entry->GetLevel()]); } @@ -739,8 +737,7 @@ void gbtTreeLayout::UpdateTableInfosets() // now add the needed length to each level, and set maxX accordingly m_maxX = 0; - for (int pos = 1; pos <= m_nodeList.Length(); pos++) { - gbtNodeEntry *entry = m_nodeList[pos]; + for (const auto &entry : m_nodeList) { if (entry->GetLevel() != 0) { entry->SetX(entry->X() + (nums[entry->GetLevel() - 1] + entry->GetSublevel()) * m_infosetSpacing); @@ -751,18 +748,17 @@ void gbtTreeLayout::UpdateTableInfosets() void gbtTreeLayout::UpdateTableParents() { - for (int pos = 1; pos <= m_nodeList.Length(); pos++) { - gbtNodeEntry *e = m_nodeList[pos]; + for (const auto &e : m_nodeList) { e->SetParent((e->GetNode() == m_doc->GetGame()->GetRoot()) ? e : GetValidParent(e->GetNode())); } } -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; - if (m_nodeList.Length() != m_doc->GetGame()->NumNodes()) { + if (m_nodeList.size() != m_doc->GetGame()->NumNodes()) { // A rebuild is in order; force it BuildNodeList(p_support); } @@ -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,12 +809,12 @@ 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) { - while (m_nodeList.Length() > 0) { - delete m_nodeList.Remove(1); + for (auto entry : m_nodeList) { + delete entry; } - + m_nodeList.clear(); m_maxLevel = 0; BuildNodeList(m_doc->GetGame()->GetRoot(), p_support, 0); } @@ -826,8 +822,7 @@ void gbtTreeLayout::BuildNodeList(const Gambit::BehaviorSupportProfile &p_suppor void gbtTreeLayout::GenerateLabels() { const gbtStyle &settings = m_doc->GetStyle(); - for (int i = 1; i <= m_nodeList.Length(); i++) { - gbtNodeEntry *entry = m_nodeList[i]; + for (const auto &entry : m_nodeList) { entry->SetNodeAboveLabel(CreateNodeLabel(entry, settings.NodeAboveLabel())); entry->SetNodeAboveFont(settings.GetFont()); entry->SetNodeBelowLabel(CreateNodeLabel(entry, settings.NodeBelowLabel())); @@ -838,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 &) { @@ -877,8 +872,7 @@ void gbtTreeLayout::RenderSubtree(wxDC &p_dc, bool p_noHints) const { const gbtStyle &settings = m_doc->GetStyle(); - for (int pos = 1; pos <= m_nodeList.Length(); pos++) { - gbtNodeEntry *entry = m_nodeList[pos]; + for (const auto &entry : m_nodeList) { gbtNodeEntry *parentEntry = entry->GetParent(); if (entry->GetChildNumber() == 1) { @@ -886,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) { @@ -942,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/efglayout.h b/src/gui/efglayout.h index d2633556c..3df490eb5 100644 --- a/src/gui/efglayout.h +++ b/src/gui/efglayout.h @@ -155,7 +155,7 @@ class gbtEfgDisplay; class gbtTreeLayout : public gbtGameView { private: /* gbtEfgDisplay *m_parent; */ - Gambit::Array m_nodeList; + std::list m_nodeList; mutable int m_maxX{0}, m_maxY{0}, m_maxLevel{0}; int m_infosetSpacing; diff --git a/src/gui/efgpanel.cc b/src/gui/efgpanel.cc index 8d970ad54..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); } @@ -582,30 +582,28 @@ gbtTreePlayerToolbar::gbtTreePlayerToolbar(wxWindow *p_parent, gbtGameDocument * void gbtTreePlayerToolbar::OnUpdate() { - while (m_playerPanels.Length() < m_doc->NumPlayers()) { - auto *panel = new gbtTreePlayerPanel(this, m_doc, m_playerPanels.Length() + 1); + while (m_playerPanels.size() < m_doc->NumPlayers()) { + auto *panel = new gbtTreePlayerPanel(this, m_doc, m_playerPanels.size() + 1); m_playerPanels.push_back(panel); GetSizer()->Add(panel, 0, wxALL | wxEXPAND, 5); } - while (m_playerPanels.Length() > m_doc->NumPlayers()) { - gbtTreePlayerPanel *panel = m_playerPanels.Remove(m_playerPanels.Length()); + while (m_playerPanels.size() > m_doc->NumPlayers()) { + gbtTreePlayerPanel *panel = m_playerPanels.back(); GetSizer()->Detach(panel); panel->Destroy(); + m_playerPanels.pop_back(); } - for (int pl = 1; pl <= m_playerPanels.Length(); 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.Length(); pl++) { - m_playerPanels[pl]->PostPendingChanges(); - } + std::for_each(m_playerPanels.begin(), m_playerPanels.end(), + std::mem_fn(&gbtTreePlayerPanel::PostPendingChanges)); } //===================================================================== @@ -622,8 +620,8 @@ END_EVENT_TABLE() gbtEfgPanel::gbtEfgPanel(wxWindow *p_parent, gbtGameDocument *p_doc) : wxPanel(p_parent, wxID_ANY), gbtGameView(p_doc), m_treeWindow(new gbtEfgDisplay(this, m_doc)), - m_playerToolbar(new gbtTreePlayerToolbar(this, m_doc)), - m_dominanceToolbar(new gbtBehavDominanceToolbar(this, m_doc)) + m_dominanceToolbar(new gbtBehavDominanceToolbar(this, m_doc)), + m_playerToolbar(new gbtTreePlayerToolbar(this, m_doc)) { auto *topSizer = new wxBoxSizer(wxVERTICAL); topSizer->Add(m_dominanceToolbar, 0, wxEXPAND, 0); @@ -715,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 @@ -727,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 df9793763..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.Length(); 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.Length(); 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; @@ -70,7 +62,7 @@ void gbtBehavDominanceStack::Reset() bool gbtBehavDominanceStack::NextLevel() { - if (m_current < m_supports.Length()) { + if (m_current < m_supports.size()) { m_current++; return true; } @@ -79,17 +71,10 @@ bool gbtBehavDominanceStack::NextLevel() return false; } - Gambit::Array players; - for (int pl = 1; pl <= m_doc->GetGame()->NumPlayers(); pl++) { - players.push_back(pl); - } - - std::ostringstream gnull; - Gambit::BehaviorSupportProfile newSupport = - m_supports[m_current]->Undominated(m_strict, true, players, gnull); + 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; } @@ -120,12 +105,6 @@ gbtStrategyDominanceStack::gbtStrategyDominanceStack(gbtGameDocument *p_doc, boo Reset(); } -gbtStrategyDominanceStack::~gbtStrategyDominanceStack() -{ - for (int i = 1; i <= m_supports.Length(); delete m_supports[i++]) - ; -} - void gbtStrategyDominanceStack::SetStrict(bool p_strict) { if (m_strict != p_strict) { @@ -136,17 +115,15 @@ void gbtStrategyDominanceStack::SetStrict(bool p_strict) void gbtStrategyDominanceStack::Reset() { - for (int i = 1; i <= m_supports.Length(); 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; } bool gbtStrategyDominanceStack::NextLevel() { - if (m_current < m_supports.Length()) { + if (m_current < m_supports.size()) { m_current++; return true; } @@ -155,16 +132,10 @@ bool gbtStrategyDominanceStack::NextLevel() return false; } - Gambit::Array players; - for (int pl = 1; pl <= m_doc->GetGame()->NumPlayers(); pl++) { - players.push_back(pl); - } - - Gambit::StrategySupportProfile newSupport = - m_supports[m_current]->Undominated(m_strict, players); + 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; } @@ -189,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) { @@ -227,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; @@ -238,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; @@ -253,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()) { @@ -273,19 +244,19 @@ 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); } } } - m_currentProfileList = m_profiles.Length(); + m_currentProfileList = m_profiles.size(); TiXmlNode *colors = docroot->FirstChild("colors"); if (colors) { @@ -348,9 +319,8 @@ void gbtGameDocument::SaveDocument(std::ostream &p_file) const p_file << "\n"; } - for (int i = 1; i <= m_profiles.Length(); 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"; @@ -376,31 +346,41 @@ 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.Length() > 0) { - delete m_profiles.Remove(1); - } + m_profiles.clear(); m_currentProfileList = 0; } - for (int i = 1; i <= m_views.Length(); 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.Length(); 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.Length(); 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) { m_style = p_style; @@ -423,12 +403,10 @@ void gbtGameDocument::Undo() m_game = nullptr; - while (m_profiles.Length() > 0) { - delete m_profiles.Remove(1); - } + 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(); @@ -436,8 +414,7 @@ void gbtGameDocument::Undo() LoadDocument(tempfile, false); wxRemoveFile(tempfile); - for (int i = 1; i <= m_views.Length(); m_views[i++]->OnUpdate()) - ; + std::for_each(m_views.begin(), m_views.end(), std::mem_fn(&gbtGameView::OnUpdate)); } void gbtGameDocument::Redo() @@ -447,12 +424,10 @@ void gbtGameDocument::Redo() m_game = nullptr; - while (m_profiles.Length() > 0) { - delete m_profiles.Remove(1); - } + 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(); @@ -460,8 +435,7 @@ void gbtGameDocument::Redo() LoadDocument(tempfile, false); wxRemoveFile(tempfile); - for (int i = 1; i <= m_views.Length(); m_views[i++]->OnUpdate()) - ; + std::for_each(m_views.begin(), m_views.end(), [](gbtGameView *v) { v->OnUpdate(); }); } void gbtGameDocument::SetCurrentProfile(int p_profile) @@ -470,10 +444,10 @@ 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.Length(); + m_currentProfileList = m_profiles.size(); UpdateViews(GBT_DOC_MODIFIED_VIEWS); } @@ -484,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++) { @@ -495,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++) { @@ -513,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()); @@ -529,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; } @@ -560,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; } @@ -581,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); @@ -624,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"); @@ -634,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); } @@ -677,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); } @@ -698,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); } @@ -747,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); } @@ -761,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); } } @@ -770,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); } @@ -789,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); } @@ -798,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 ff83e6b6e..5c76153a8 100644 --- a/src/gui/gamedoc.h +++ b/src/gui/gamedoc.h @@ -40,29 +40,29 @@ 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 //! - int NumSupports() const { return m_supports.Length(); } + int NumSupports() const { return m_supports.size(); } //! //! 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) @@ -100,7 +100,7 @@ class gbtBehavDominanceStack { //! //! Returns 'false' if it is known that no further eliminations can be done //! - bool CanEliminate() const { return (m_current < m_supports.Length() || !m_noFurther); } + bool CanEliminate() const { return (m_current < m_supports.size() || !m_noFurther); } }; //! @@ -111,29 +111,29 @@ 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 //! - int NumSupports() const { return m_supports.Length(); } + int NumSupports() const { return m_supports.size(); } //! //! 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) @@ -176,7 +176,7 @@ class gbtStrategyDominanceStack { //! //! Returns 'false' if it is known that no further eliminations can be done //! - bool CanEliminate() const { return (m_current < m_supports.Length() || !m_noFurther); } + bool CanEliminate() const { return (m_current < m_supports.size() || !m_noFurther); } }; // @@ -211,28 +211,28 @@ 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) { - m_views.Remove(m_views.Find(p_view)); - if (m_views.Length() == 0) { + m_views.erase(std::find(m_views.begin(), m_views.end(), p_view)); + if (m_views.empty()) { delete this; } } - 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,21 +287,21 @@ 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.Length(); } + int NumProfileLists() const { return m_profiles.size(); } int GetCurrentProfileList() const { return m_currentProfileList; } int GetCurrentProfile() const { - return (m_profiles.Length() == 0) ? 0 : GetProfiles().GetCurrent(); + return (m_profiles.size() == 0) ? 0 : GetProfiles().GetCurrent(); } 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 12b5af8d9..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().Length(); 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 569238229..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); } @@ -278,30 +278,28 @@ gbtTablePlayerToolbar::gbtTablePlayerToolbar(gbtNfgPanel *p_parent, gbtGameDocum void gbtTablePlayerToolbar::OnUpdate() { - while (m_playerPanels.Length() < m_doc->NumPlayers()) { - auto *panel = new gbtTablePlayerPanel(this, m_nfgPanel, m_doc, m_playerPanels.Length() + 1); + while (m_playerPanels.size() < m_doc->NumPlayers()) { + auto *panel = new gbtTablePlayerPanel(this, m_nfgPanel, m_doc, m_playerPanels.size() + 1); m_playerPanels.push_back(panel); GetSizer()->Add(panel, 0, wxALL | wxEXPAND, 5); } - while (m_playerPanels.Length() > m_doc->NumPlayers()) { - gbtTablePlayerPanel *panel = m_playerPanels.Remove(m_playerPanels.Length()); + while (m_playerPanels.size() > m_doc->NumPlayers()) { + gbtTablePlayerPanel *panel = m_playerPanels.back(); GetSizer()->Detach(panel); panel->Destroy(); + m_playerPanels.pop_back(); } - for (int pl = 1; pl <= m_playerPanels.Length(); 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.Length(); pl++) { - m_playerPanels[pl]->PostPendingChanges(); - } + std::for_each(m_playerPanels.begin(), m_playerPanels.end(), + std::mem_fn(&gbtTablePlayerPanel::PostPendingChanges)); } //===================================================================== @@ -434,8 +432,8 @@ END_EVENT_TABLE() gbtNfgPanel::gbtNfgPanel(wxWindow *p_parent, gbtGameDocument *p_doc) : wxPanel(p_parent, wxID_ANY), gbtGameView(p_doc), m_dominanceToolbar(new gbtStrategyDominanceToolbar(this, m_doc)), - m_tableWidget(new gbtTableWidget(this, wxID_ANY, m_doc)), - m_playerToolbar(new gbtTablePlayerToolbar(this, m_doc)) + m_playerToolbar(new gbtTablePlayerToolbar(this, m_doc)), + m_tableWidget(new gbtTableWidget(this, wxID_ANY, m_doc)) { auto *playerSizer = new wxBoxSizer(wxHORIZONTAL); playerSizer->Add(m_playerToolbar, 0, wxEXPAND, 0); 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 96ac341f9..9cc7d807e 100644 --- a/src/gui/nfgtable.cc +++ b/src/gui/nfgtable.cc @@ -194,6 +194,12 @@ gbtRowPlayerWidget::gbtRowPlayerWidget(gbtTableWidget *p_parent, gbtGameDocument wxSheetEventFunction, wxSheetEventFunction(&gbtRowPlayerWidget::OnCellRightClick)))); } +GameStrategy GetStrategy(const StrategySupportProfile &p_profile, int pl, int st) +{ + auto strategies = p_profile.GetStrategies(p_profile.GetGame()->GetPlayer(pl)); + return *std::next(strategies.begin(), st - 1); +} + void gbtRowPlayerWidget::OnCellRightClick(wxSheetEvent &p_event) { if (m_table->NumRowPlayers() == 0 || m_doc->GetGame()->IsTree()) { @@ -202,12 +208,12 @@ 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(support.GetStrategy(player, strat)); + m_doc->DoDeleteStrategy(GetStrategy(support, player, strat)); } wxString gbtRowPlayerWidget::GetCellValue(const wxSheetCoords &p_coords) @@ -222,20 +228,20 @@ 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 {support.GetStrategy(player, strat)->GetLabel().c_str(), *wxConvCurrent}; + return {GetStrategy(support, player, strat)->GetLabel().c_str(), *wxConvCurrent}; } void gbtRowPlayerWidget::SetCellValue(const wxSheetCoords &p_coords, const wxString &p_value) { 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(support.GetStrategy(player, strat), p_value); + m_doc->DoSetStrategyLabel(GetStrategy(support, player, strat), p_value); } wxSheetCellAttr gbtRowPlayerWidget::GetAttr(const wxSheetCoords &p_coords, wxSheetAttr_Type) const @@ -266,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 = support.GetStrategy(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)); } @@ -285,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()); } @@ -293,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()); } @@ -309,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()) { @@ -333,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); @@ -420,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(support.GetStrategy(player, strat)); + 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()); } @@ -438,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()); } @@ -454,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()) { @@ -478,20 +484,20 @@ 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 {support.GetStrategy(player, strat)->GetLabel().c_str(), *wxConvCurrent}; + return {GetStrategy(support, player, strat)->GetLabel().c_str(), *wxConvCurrent}; } void gbtColPlayerWidget::SetCellValue(const wxSheetCoords &p_coords, const wxString &p_value) { 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(support.GetStrategy(player, strat), p_value); + m_doc->DoSetStrategyLabel(GetStrategy(support, player, strat), p_value); } wxSheetCellAttr gbtColPlayerWidget::GetAttr(const wxSheetCoords &p_coords, wxSheetAttr_Type) const @@ -522,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 = support.GetStrategy(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)); } @@ -551,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); @@ -632,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); } @@ -643,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()); } @@ -651,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()); } @@ -668,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}; } @@ -682,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); } @@ -708,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())); @@ -720,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; } @@ -749,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); @@ -922,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(); @@ -932,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(); @@ -947,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(); @@ -957,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(); @@ -991,23 +999,23 @@ void gbtTableWidget::OnBeginEdit(wxSheetEvent &) { m_doc->PostPendingChanges(); void gbtTableWidget::OnUpdate() { - if (m_doc->NumPlayers() > m_rowPlayers.Length() + m_colPlayers.Length()) { - for (int pl = 1; pl <= m_doc->NumPlayers(); pl++) { - if (!m_rowPlayers.Contains(pl) && !m_colPlayers.Contains(pl)) { + if (m_doc->NumPlayers() > m_rowPlayers.size() + m_colPlayers.size()) { + 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.Length() + m_colPlayers.Length()) { - for (int i = 1; i <= m_rowPlayers.Length(); i++) { - if (m_rowPlayers[i] > m_doc->NumPlayers()) { - m_rowPlayers.Remove(i--); + else if (m_doc->NumPlayers() < m_rowPlayers.size() + m_colPlayers.size()) { + 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.Length(); i++) { - if (m_colPlayers[i] > m_doc->NumPlayers()) { - m_colPlayers.Remove(i--); + for (size_t i = 1; i <= m_colPlayers.size(); i++) { + if (m_colPlayers[i] > static_cast(m_doc->NumPlayers())) { + erase_atindex(m_colPlayers, i--); } } } @@ -1054,28 +1062,32 @@ bool gbtTableWidget::ShowDominance() const { return m_nfgPanel->IsDominanceShown void gbtTableWidget::SetRowPlayer(int index, int pl) { - if (m_rowPlayers.Contains(pl)) { - int oldIndex = m_rowPlayers.Find(pl); - m_rowPlayers.Remove(oldIndex); - if (index > oldIndex) { - m_rowPlayers.Insert(pl, index - 1); + if (contains(m_colPlayers, pl)) { + m_colPlayers.erase(std::find(m_colPlayers.begin(), m_colPlayers.end(), pl)); + } + Array newPlayers; + for (const auto &player : m_rowPlayers) { + if (static_cast(newPlayers.size()) == index - 1) { + newPlayers.push_back(pl); } - else { - m_rowPlayers.Insert(pl, index); + else if (player != pl) { + newPlayers.push_back(player); } } - else { - m_colPlayers.Remove(m_colPlayers.Find(pl)); - m_rowPlayers.Insert(pl, index); - } + m_rowPlayers = newPlayers; OnUpdate(); } +int NumStrategies(const StrategySupportProfile &p_profile, int p_player) +{ + return p_profile.GetStrategies(p_profile.GetGame()->GetPlayer(p_player)).size(); +} + int gbtTableWidget::NumRowContingencies() const { int ncont = 1; const Gambit::StrategySupportProfile &support = m_doc->GetNfgSupport(); - for (int i = 1; i <= NumRowPlayers(); ncont *= support.NumStrategies(GetRowPlayer(i++))) + for (int i = 1; i <= NumRowPlayers(); ncont *= NumStrategies(support, GetRowPlayer(i++))) ; return ncont; } @@ -1084,33 +1096,32 @@ int gbtTableWidget::NumRowsSpanned(int index) const { int ncont = 1; const Gambit::StrategySupportProfile &support = m_doc->GetNfgSupport(); - for (int i = index + 1; i <= NumRowPlayers(); ncont *= support.NumStrategies(GetRowPlayer(i++))) + for (int i = index + 1; i <= NumRowPlayers(); ncont *= NumStrategies(support, GetRowPlayer(i++))) ; return ncont; } int gbtTableWidget::RowToStrategy(int player, int row) const { - int strat = row / NumRowsSpanned(player); - return (strat % m_doc->GetNfgSupport().NumStrategies(GetRowPlayer(player)) + 1); + const int strat = row / NumRowsSpanned(player); + return (strat % NumStrategies(m_doc->GetNfgSupport(), GetRowPlayer(player)) + 1); } void gbtTableWidget::SetColPlayer(int index, int pl) { - if (m_colPlayers.Contains(pl)) { - int oldIndex = m_colPlayers.Find(pl); - m_colPlayers.Remove(oldIndex); - if (index > oldIndex) { - m_colPlayers.Insert(pl, index - 1); + if (contains(m_rowPlayers, pl)) { + m_rowPlayers.erase(std::find(m_rowPlayers.begin(), m_rowPlayers.end(), pl)); + } + Array newPlayers; + for (const auto &player : m_colPlayers) { + if (static_cast(newPlayers.size()) == index - 1) { + newPlayers.push_back(pl); } - else { - m_colPlayers.Insert(pl, index); + else if (player != pl) { + newPlayers.push_back(player); } } - else { - m_rowPlayers.Remove(m_rowPlayers.Find(pl)); - m_colPlayers.Insert(pl, index); - } + m_colPlayers = newPlayers; OnUpdate(); } @@ -1118,7 +1129,7 @@ int gbtTableWidget::NumColContingencies() const { int ncont = 1; const Gambit::StrategySupportProfile &support = m_doc->GetNfgSupport(); - for (int i = 1; i <= NumColPlayers(); ncont *= support.NumStrategies(GetColPlayer(i++))) + for (int i = 1; i <= NumColPlayers(); ncont *= NumStrategies(support, GetColPlayer(i++))) ; return ncont; } @@ -1127,30 +1138,30 @@ int gbtTableWidget::NumColsSpanned(int index) const { int ncont = 1; const Gambit::StrategySupportProfile &support = m_doc->GetNfgSupport(); - for (int i = index + 1; i <= NumColPlayers(); ncont *= support.NumStrategies(GetColPlayer(i++))) + for (int i = index + 1; i <= NumColPlayers(); ncont *= NumStrategies(support, GetColPlayer(i++))) ; return ncont; } int gbtTableWidget::ColToStrategy(int player, int col) const { - int strat = col / m_doc->NumPlayers() / NumColsSpanned(player); - return (strat % m_doc->GetNfgSupport().NumStrategies(GetColPlayer(player)) + 1); + const int strat = col / m_doc->NumPlayers() / NumColsSpanned(player); + return (strat % NumStrategies(m_doc->GetNfgSupport(), GetColPlayer(player)) + 1); } Gambit::PureStrategyProfile gbtTableWidget::CellToProfile(const wxSheetCoords &p_coords) const { 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); - profile->SetStrategy(support.GetStrategy(player, RowToStrategy(i, p_coords.GetRow()))); + 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); - profile->SetStrategy(support.GetStrategy(player, ColToStrategy(i, p_coords.GetCol()))); + const int player = GetColPlayer(i); + profile->SetStrategy(GetStrategy(support, player, ColToStrategy(i, p_coords.GetCol()))); } return profile; @@ -1189,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); @@ -1213,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); @@ -1231,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()); @@ -1243,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); @@ -1255,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/nfgtable.h b/src/gui/nfgtable.h index 903fb6374..8b27fad87 100644 --- a/src/gui/nfgtable.h +++ b/src/gui/nfgtable.h @@ -88,7 +88,7 @@ class gbtTableWidget : public wxPanel { /// @name View state //@{ /// Returns the number of players assigned to the rows - int NumRowPlayers() const { return m_rowPlayers.Length(); } + int NumRowPlayers() const { return m_rowPlayers.size(); } /// Returns the index'th player assigned to the rows (1=slowest incrementing) int GetRowPlayer(int index) const { return m_rowPlayers[index]; } @@ -106,7 +106,7 @@ class gbtTableWidget : public wxPanel { int RowToStrategy(int player, int row) const; /// Returns the number of players assigned to the columns - int NumColPlayers() const { return m_colPlayers.Length(); } + int NumColPlayers() const { return m_colPlayers.size(); } /// Returns the index'th player assigned to the columns (1=slowest) int GetColPlayer(int index) const { return m_colPlayers[index]; } 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 99fe67487..01cf49960 100644 --- a/src/gui/style.cc +++ b/src/gui/style.cc @@ -50,8 +50,8 @@ static wxColour s_defaultColors[8] = { //! const wxColour &gbtStyle::GetPlayerColor(int pl) const { - while (pl > m_playerColors.Length()) { - m_playerColors.push_back(s_defaultColors[m_playerColors.Length() % 8]); + while (pl > static_cast(m_playerColors.size())) { + m_playerColors.push_back(s_defaultColors[m_playerColors.size() % 8]); } return m_playerColors[pl]; @@ -81,7 +81,7 @@ void gbtStyle::SetDefaults() m_chanceColor = wxColour(154, 205, 50); m_terminalColor = *wxBLACK; - for (int pl = 1; pl <= m_playerColors.Length(); 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.Length(); 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/labenski/src/plotdata.cpp b/src/labenski/src/plotdata.cpp index cfad21bf1..68cec7c48 100644 --- a/src/labenski/src/plotdata.cpp +++ b/src/labenski/src/plotdata.cpp @@ -820,10 +820,10 @@ bool wxPlotData::LoadFile(const wxString &filename, int x_col, int y_col, int op wxMessageBox( wxString::Format( - wxT("Loading cols (%d,%d) aborted after %d points\n\n") - wxT("First 100 characters of offending line number: %d\n") wxT("\"%s\"\n\n") - wxT("# for comments, blank lines Ok, comma, tab, space for separators\n") - wxT("7 4\n33 2.5e-2\n...\n"), + wxT("Loading cols (%d,%d) aborted after %d points\n\n") wxT( + "first_index 100 characters of offending line number: %d\n") wxT("\"%s\"\n\n") + wxT("# for comments, blank lines Ok, comma, tab, space for separators\n") + wxT("7 4\n33 2.5e-2\n...\n"), x_col, y_col, points, line_number, wxstr.Left(100).c_str()), wxT("Error loading ") + filename, wxOK | wxICON_ERROR); stop_load = true; diff --git a/src/pygambit/__init__.py b/src/pygambit/__init__.py index ed7316321..8d399dfa9 100644 --- a/src/pygambit/__init__.py +++ b/src/pygambit/__init__.py @@ -23,10 +23,9 @@ from .gambit import * # noqa: F401,F403,I001 from . import ( # noqa: F401 - gte, # noqa: F401 nash, # noqa: F401 qre, # noqa: F401 supports, # noqa: F401 ) -__version__ = "16.2.1" +__version__ = "16.3.0" 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/behavmixed.pxi b/src/pygambit/behavmixed.pxi index ad1a72ec5..e37d465f7 100644 --- a/src/pygambit/behavmixed.pxi +++ b/src/pygambit/behavmixed.pxi @@ -867,11 +867,11 @@ class MixedBehaviorProfile: @cython.cclass class MixedBehaviorProfileDouble(MixedBehaviorProfile): - profile = cython.declare(shared_ptr[c_MixedBehaviorProfileDouble]) + profile = cython.declare(shared_ptr[c_MixedBehaviorProfile[double]]) @staticmethod @cython.cfunc - def wrap(profile: shared_ptr[c_MixedBehaviorProfileDouble]) -> MixedBehaviorProfileDouble: + def wrap(profile: shared_ptr[c_MixedBehaviorProfile[float]]) -> MixedBehaviorProfileDouble: obj: MixedBehaviorProfileDouble = ( MixedBehaviorProfileDouble.__new__(MixedBehaviorProfileDouble) ) @@ -932,11 +932,11 @@ class MixedBehaviorProfileDouble(MixedBehaviorProfile): def _copy(self) -> MixedBehaviorProfileDouble: return MixedBehaviorProfileDouble.wrap( - make_shared[c_MixedBehaviorProfileDouble](deref(self.profile)) + make_shared[c_MixedBehaviorProfile[double]](deref(self.profile)) ) def _as_strategy(self) -> MixedStrategyProfileDouble: - return MixedStrategyProfileDouble.wrap(make_shared[c_MixedStrategyProfileDouble]( + return MixedStrategyProfileDouble.wrap(make_shared[c_MixedStrategyProfile[double]]( deref(self.profile).ToMixedProfile() )) @@ -945,7 +945,7 @@ class MixedBehaviorProfileDouble(MixedBehaviorProfile): def _normalize(self) -> MixedBehaviorProfileDouble: return MixedBehaviorProfileDouble.wrap( - make_shared[c_MixedBehaviorProfileDouble](deref(self.profile).Normalize()) + make_shared[c_MixedBehaviorProfile[double]](deref(self.profile).Normalize()) ) @property @@ -955,11 +955,13 @@ class MixedBehaviorProfileDouble(MixedBehaviorProfile): @cython.cclass class MixedBehaviorProfileRational(MixedBehaviorProfile): - profile = cython.declare(shared_ptr[c_MixedBehaviorProfileRational]) + profile = cython.declare(shared_ptr[c_MixedBehaviorProfile[c_Rational]]) @staticmethod @cython.cfunc - def wrap(profile: shared_ptr[c_MixedBehaviorProfileRational]) -> MixedBehaviorProfileRational: + def wrap( + profile: shared_ptr[c_MixedBehaviorProfile[c_Rational]] + ) -> MixedBehaviorProfileRational: obj: MixedBehaviorProfileRational = ( MixedBehaviorProfileRational.__new__(MixedBehaviorProfileRational) ) @@ -1026,11 +1028,11 @@ class MixedBehaviorProfileRational(MixedBehaviorProfile): def _copy(self) -> MixedBehaviorProfileRational: return MixedBehaviorProfileRational.wrap( - make_shared[c_MixedBehaviorProfileRational](deref(self.profile)) + make_shared[c_MixedBehaviorProfile[c_Rational]](deref(self.profile)) ) def _as_strategy(self) -> MixedStrategyProfileRational: - return MixedStrategyProfileRational.wrap(make_shared[c_MixedStrategyProfileRational]( + return MixedStrategyProfileRational.wrap(make_shared[c_MixedStrategyProfile[c_Rational]]( deref(self.profile).ToMixedProfile() )) @@ -1039,7 +1041,7 @@ class MixedBehaviorProfileRational(MixedBehaviorProfile): def _normalize(self) -> MixedBehaviorProfileRational: return MixedBehaviorProfileRational.wrap( - make_shared[c_MixedBehaviorProfileRational](deref(self.profile).Normalize()) + make_shared[c_MixedBehaviorProfile[c_Rational]](deref(self.profile).Normalize()) ) @property diff --git a/src/pygambit/gambit.pxd b/src/pygambit/gambit.pxd index 86ede77cc..affe12f0f 100644 --- a/src/pygambit/gambit.pxd +++ b/src/pygambit/gambit.pxd @@ -1,6 +1,8 @@ from libcpp cimport bool from libcpp.string cimport string -from libcpp.memory cimport shared_ptr +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": @@ -12,8 +14,8 @@ cdef extern from "gambit.h": cdef extern from "core/rational.h": cdef cppclass c_Rational "Rational": c_Rational() except + - string rat_str "lexical_cast"(c_Rational) except + - c_Rational str_rat "lexical_cast"(string) except + + string to_string "lexical_cast"(c_Rational) except + + c_Rational to_rational "lexical_cast"(string) except + cdef extern from "games/number.h": @@ -25,15 +27,24 @@ cdef extern from "games/number.h": cdef extern from "core/array.h": cdef cppclass Array[T]: + cppclass iterator: + T operator*() + iterator operator++() + bint operator==(iterator) + bint operator!=(iterator) T getitem "operator[]"(int) except + - int Length() except + + int size() except + Array() except + Array(int) except + + void push_back(T) except + + iterator begin() except + + iterator end() except + + cdef extern from "core/list.h": cdef cppclass c_List "List"[T]: T & getitem "operator[]"(int) except + - int Length() except + + int size() except + void push_back(T) except + cdef extern from "games/game.h": @@ -62,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 @@ -73,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": @@ -85,41 +98,75 @@ cdef extern from "games/game.h": c_GamePlayer GetPlayer() except + string GetLabel() except + void SetLabel(string) except + - void DeleteStrategy() except + + c_GameAction GetAction(c_GameInfoset) except + cdef cppclass c_GameActionRep "GameActionRep": 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 + @@ -127,12 +174,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 + @@ -141,10 +187,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 + @@ -152,14 +208,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 + @@ -167,16 +220,36 @@ 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: + cppclass iterator: + bint operator ==(iterator) + bint operator !=(iterator) + c_GameNode operator *() + iterator operator++() + iterator begin() except + + iterator end() except + + int IsTree() except + string GetTitle() except + @@ -187,18 +260,24 @@ cdef extern from "games/game.h": int NumPlayers() except + c_GamePlayer GetPlayer(int) except +IndexError + 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 + + Nodes GetNodes() 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 @@ -208,20 +287,37 @@ 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 + c_PureStrategyProfile NewPureStrategyProfile() # except + doesn't compile - c_MixedStrategyProfileDouble NewMixedStrategyProfile(double) # except + doesn't compile - c_MixedStrategyProfileRational NewMixedStrategyProfile( - c_Rational - ) # 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": @@ -235,101 +331,66 @@ cdef extern from "games/stratpure.h": c_Rational GetPayoff(c_GamePlayer) except + -cdef extern from "games/stratmixed.h": - cdef cppclass c_MixedStrategyProfileDouble "MixedStrategyProfile": - bool operator==(c_MixedStrategyProfileDouble) except + - bool operator!=(c_MixedStrategyProfileDouble) except + +cdef extern from "games/stratmixed.h" namespace "Gambit": + cdef cppclass c_MixedStrategyProfile "MixedStrategyProfile"[T]: + bool operator==(c_MixedStrategyProfile[T]) except + + bool operator!=(c_MixedStrategyProfile[T]) except + c_Game GetGame() except + bool IsInvalidated() int MixedProfileLength() except + c_StrategySupportProfile GetSupport() except + - c_MixedStrategyProfileDouble Normalize() # except + doesn't compile - double getitem_strategy "operator[]"(c_GameStrategy) except +IndexError - double GetPayoff(c_GamePlayer) except + - double GetPayoff(c_GameStrategy) except + - double GetRegret(c_GameStrategy) except + - double GetRegret(c_GamePlayer) except + - double GetMaxRegret() except + - double GetPayoffDeriv(int, c_GameStrategy, c_GameStrategy) except + - double GetLiapValue() except + - c_MixedStrategyProfileDouble ToFullSupport() except + - c_MixedStrategyProfileDouble(c_MixedStrategyProfileDouble) except + - - cdef cppclass c_MixedStrategyProfileRational "MixedStrategyProfile": - bool operator==(c_MixedStrategyProfileRational) except + - bool operator!=(c_MixedStrategyProfileRational) except + - c_Game GetGame() except + - bool IsInvalidated() - int MixedProfileLength() except + - c_StrategySupportProfile GetSupport() except + - c_MixedStrategyProfileRational Normalize() # except + doesn't compile - c_Rational getitem_strategy "operator[]"(c_GameStrategy) except +IndexError - c_Rational GetPayoff(c_GamePlayer) except + - c_Rational GetPayoff(c_GameStrategy) except + - c_Rational GetRegret(c_GameStrategy) except + - c_Rational GetRegret(c_GamePlayer) except + - c_Rational GetMaxRegret() except + - c_Rational GetPayoffDeriv(int, c_GameStrategy, c_GameStrategy) except + - c_Rational GetLiapValue() except + - c_MixedStrategyProfileRational ToFullSupport() except + - c_MixedStrategyProfileRational(c_MixedStrategyProfileRational) except + - -cdef extern from "games/behavmixed.h": - cdef cppclass c_MixedBehaviorProfileDouble "MixedBehaviorProfile": - bool operator==(c_MixedBehaviorProfileDouble) except + - bool operator!=(c_MixedBehaviorProfileDouble) except + - c_Game GetGame() except + - bool IsInvalidated() - int BehaviorProfileLength() except + - bool IsDefinedAt(c_GameInfoset) except + - c_MixedBehaviorProfileDouble Normalize() # except + doesn't compile - double getitem "operator[]"(int) except +IndexError - double getaction "operator[]"(c_GameAction) except +IndexError - double GetPayoff(int) except + - double GetBeliefProb(c_GameNode) except + - double GetRealizProb(c_GameNode) except + - double GetInfosetProb(c_GameInfoset) except + - double GetPayoff(c_GameInfoset) except + - double GetPayoff(c_GamePlayer, c_GameNode) except + - double GetPayoff(c_GameAction) except + - double GetRegret(c_GameAction) except + - double GetRegret(c_GameInfoset) except + - double GetMaxRegret() except + - double GetLiapValue() except + - c_MixedStrategyProfileDouble ToMixedProfile() # except + doesn't compile - c_MixedBehaviorProfileDouble(c_MixedStrategyProfileDouble) except +NotImplementedError - c_MixedBehaviorProfileDouble(c_Game) except + - c_MixedBehaviorProfileDouble(c_MixedBehaviorProfileDouble) except + - - cdef cppclass c_MixedBehaviorProfileRational "MixedBehaviorProfile": - bool operator==(c_MixedBehaviorProfileRational) except + - bool operator!=(c_MixedBehaviorProfileRational) except + + c_MixedStrategyProfile[T] Normalize() # except + doesn't compile + T getitem_strategy "operator[]"(c_GameStrategy) except +IndexError + T GetPayoff(c_GamePlayer) except + + T GetPayoff(c_GameStrategy) except + + T GetRegret(c_GameStrategy) except + + T GetRegret(c_GamePlayer) except + + T GetMaxRegret() except + + T GetPayoffDeriv(int, c_GameStrategy, c_GameStrategy) except + + T GetLiapValue() except + + c_MixedStrategyProfile[T] ToFullSupport() except + + c_MixedStrategyProfile(c_MixedStrategyProfile[T]) except + + +cdef extern from "games/behavmixed.h" namespace "Gambit": + cdef cppclass c_MixedBehaviorProfile "MixedBehaviorProfile"[T]: + bool operator==(c_MixedBehaviorProfile[T]) except + + bool operator!=(c_MixedBehaviorProfile[T]) except + c_Game GetGame() except + bool IsInvalidated() int BehaviorProfileLength() except + bool IsDefinedAt(c_GameInfoset) except + - c_MixedBehaviorProfileRational Normalize() # except + doesn't compile - c_Rational getitem "operator[]"(int) except +IndexError - c_Rational getaction "operator[]"(c_GameAction) except +IndexError - c_Rational GetPayoff(int) except + - c_Rational GetBeliefProb(c_GameNode) except + - c_Rational GetRealizProb(c_GameNode) except + - c_Rational GetInfosetProb(c_GameInfoset) except + - c_Rational GetPayoff(c_GameInfoset) except + - c_Rational GetPayoff(c_GamePlayer, c_GameNode) except + - c_Rational GetPayoff(c_GameAction) except + - c_Rational GetRegret(c_GameAction) except + - c_Rational GetRegret(c_GameInfoset) except + - c_Rational GetMaxRegret() except + - c_Rational GetLiapValue() except + - c_MixedStrategyProfileRational ToMixedProfile() # except + doesn't compile - c_MixedBehaviorProfileRational(c_MixedStrategyProfileRational) except +NotImplementedError - c_MixedBehaviorProfileRational(c_Game) except + - c_MixedBehaviorProfileRational(c_MixedBehaviorProfileRational) except + - + c_MixedBehaviorProfile[T] Normalize() # except + doesn't compile + T getitem "operator[]"(int) except +IndexError + T getaction "operator[]"(c_GameAction) except +IndexError + T GetPayoff(int) except + + T GetBeliefProb(c_GameNode) except + + T GetRealizProb(c_GameNode) except + + T GetInfosetProb(c_GameInfoset) except + + T GetPayoff(c_GameInfoset) except + + T GetPayoff(c_GamePlayer, c_GameNode) except + + T GetPayoff(c_GameAction) except + + T GetRegret(c_GameAction) except + + T GetRegret(c_GameInfoset) except + + T GetMaxRegret() except + + T GetLiapValue() except + + c_MixedStrategyProfile[T] ToMixedProfile() # except + doesn't compile + c_MixedBehaviorProfile(c_MixedStrategyProfile[T]) except +NotImplementedError + c_MixedBehaviorProfile(c_Game) except + + c_MixedBehaviorProfile(c_MixedBehaviorProfile[T]) except + cdef extern from "games/stratspt.h": cdef cppclass c_StrategySupportProfile "StrategySupportProfile": + cppclass Support: + cppclass const_iterator: + c_GameStrategy operator *() + const_iterator operator++() + bint operator ==(const_iterator) + bint operator !=(const_iterator) + Support() + int size() + const_iterator begin() except + + const_iterator end() except + + c_StrategySupportProfile(c_Game) except + c_StrategySupportProfile(c_StrategySupportProfile) except + bool operator ==(c_StrategySupportProfile) except + @@ -338,15 +399,15 @@ cdef extern from "games/stratspt.h": Array[int] NumStrategies() except + int MixedProfileLength() except + int GetIndex(c_GameStrategy) except + - int NumStrategiesPlayer "NumStrategies"(int) except +IndexError bool IsSubsetOf(c_StrategySupportProfile) except + bool RemoveStrategy(c_GameStrategy) except + - c_GameStrategy GetStrategy(int, int) except +IndexError + Support GetStrategies(c_GamePlayer) except + bool Contains(c_GameStrategy) except + + bool IsDominated(c_GameStrategy, bool, bool) except + c_StrategySupportProfile Undominated(bool, bool) # except + doesn't compile - c_MixedStrategyProfileDouble NewMixedStrategyProfileDouble \ + c_MixedStrategyProfile[double] NewMixedStrategyProfileDouble \ "NewMixedStrategyProfile"() except + - c_MixedStrategyProfileRational NewMixedStrategyProfileRational \ + c_MixedStrategyProfile[c_Rational] NewMixedStrategyProfileRational \ "NewMixedStrategyProfile"() except + @@ -356,141 +417,160 @@ cdef extern from "games/behavspt.h": cdef extern from "util.h": - c_Game ReadGame(char *) except +IOError - c_Game ParseGame(char *) except +IOError - string WriteGame(c_Game, string) except +IOError - string WriteGame(c_StrategySupportProfile) except +IOError - - c_Rational to_rational(char *) except + + c_Game ParseGbtGame(string) except +IOError + c_Game ParseEfgGame(string) except +IOError + c_Game ParseNfgGame(string) except +IOError + c_Game ParseAggGame(string) except +IOError + string WriteEfgFile(c_Game) + string WriteNfgFile(c_Game) + string WriteNfgFileSupport(c_StrategySupportProfile) except +IOError + string WriteLaTeXFile(c_Game) + string WriteHTMLFile(c_Game) + + stdlist[shared_ptr[T]] make_list_of_pointer[T](stdlist[T]) except + + void setitem_array_int "setitem"(Array[int] *, int, int) except + + void setitem_array_number "setitem"(Array[c_Number], int, c_Number) except + void setitem_array_int "setitem"(Array[int] *, int, int) except + void setitem_array_number "setitem"(Array[c_Number], int, c_Number) except + - void setitem_mspd_int "setitem"(c_MixedStrategyProfileDouble, int, double) except + - void setitem_mspd_strategy "setitem"(c_MixedStrategyProfileDouble, + void setitem_mspd_int "setitem"(c_MixedStrategyProfile[double], int, double) except + + void setitem_mspd_strategy "setitem"(c_MixedStrategyProfile[double], c_GameStrategy, double) except + - void setitem_mspr_int "setitem"(c_MixedStrategyProfileRational, int, c_Rational) except + - void setitem_mspr_strategy "setitem"(c_MixedStrategyProfileRational, + void setitem_mspr_int "setitem"(c_MixedStrategyProfile[c_Rational], int, c_Rational) except + + void setitem_mspr_strategy "setitem"(c_MixedStrategyProfile[c_Rational], c_GameStrategy, c_Rational) except + - void setitem_mbpd_int "setitem"(c_MixedBehaviorProfileDouble, int, double) except + - void setitem_mbpd_action "setitem"(c_MixedBehaviorProfileDouble, + void setitem_mbpd_int "setitem"(c_MixedBehaviorProfile[double], int, double) except + + void setitem_mbpd_action "setitem"(c_MixedBehaviorProfile[double], c_GameAction, double) except + - void setitem_mbpr_int "setitem"(c_MixedBehaviorProfileRational, int, c_Rational) except + - void setitem_mbpr_action "setitem"(c_MixedBehaviorProfileRational, + void setitem_mbpr_int "setitem"(c_MixedBehaviorProfile[c_Rational], int, c_Rational) except + + void setitem_mbpr_action "setitem"(c_MixedBehaviorProfile[c_Rational], c_GameAction, c_Rational) except + - shared_ptr[c_MixedStrategyProfileDouble] copyitem_list_mspd "sharedcopyitem"( - c_List[c_MixedStrategyProfileDouble], int + shared_ptr[c_MixedStrategyProfile[double]] copyitem_list_mspd "sharedcopyitem"( + c_List[c_MixedStrategyProfile[double]], int ) except + - shared_ptr[c_MixedStrategyProfileRational] copyitem_list_mspr "sharedcopyitem"( - c_List[c_MixedStrategyProfileRational], int + shared_ptr[c_MixedStrategyProfile[c_Rational]] copyitem_list_mspr "sharedcopyitem"( + c_List[c_MixedStrategyProfile[c_Rational]], int ) except + - shared_ptr[c_MixedBehaviorProfileDouble] copyitem_list_mbpd "sharedcopyitem"( - c_List[c_MixedBehaviorProfileDouble], int + shared_ptr[c_MixedBehaviorProfile[double]] copyitem_list_mbpd "sharedcopyitem"( + c_List[c_MixedBehaviorProfile[double]], int ) except + - shared_ptr[c_MixedBehaviorProfileRational] copyitem_list_mbpr "sharedcopyitem"( - c_List[c_MixedBehaviorProfileRational], int + shared_ptr[c_MixedBehaviorProfile[c_Rational]] copyitem_list_mbpr "sharedcopyitem"( + c_List[c_MixedBehaviorProfile[c_Rational]], int ) except + shared_ptr[c_LogitQREMixedStrategyProfile] copyitem_list_qrem "sharedcopyitem"( c_List[c_LogitQREMixedStrategyProfile], int ) except + + shared_ptr[c_LogitQREMixedBehaviorProfile] copyitem_list_qreb "sharedcopyitem"( + c_List[c_LogitQREMixedBehaviorProfile], int + ) except + cdef extern from "solvers/enumpure/enumpure.h": - c_List[c_MixedStrategyProfileRational] EnumPureStrategySolve(c_Game) except +RuntimeError - c_List[c_MixedBehaviorProfileRational] EnumPureAgentSolve(c_Game) except +RuntimeError + c_List[c_MixedStrategyProfile[c_Rational]] EnumPureStrategySolve(c_Game) except +RuntimeError + c_List[c_MixedBehaviorProfile[c_Rational]] EnumPureAgentSolve(c_Game) except +RuntimeError cdef extern from "solvers/enummixed/enummixed.h": - c_List[c_MixedStrategyProfileDouble] EnumMixedStrategySolveDouble(c_Game) except +RuntimeError - c_List[c_MixedStrategyProfileRational] EnumMixedStrategySolveRational( - c_Game - ) except +RuntimeError - c_List[c_MixedStrategyProfileRational] 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_MixedStrategyProfileDouble] LcpStrategySolveDouble( - c_Game, int p_stopAfter, int p_maxDepth - ) except +RuntimeError - c_List[c_MixedStrategyProfileRational] LcpStrategySolveRational( - c_Game, int p_stopAfter, int p_maxDepth - ) except +RuntimeError - c_List[c_MixedBehaviorProfileDouble] LcpBehaviorSolveDouble( + c_List[c_MixedStrategyProfile[T]] LcpStrategySolve[T]( c_Game, int p_stopAfter, int p_maxDepth ) except +RuntimeError - c_List[c_MixedBehaviorProfileRational] LcpBehaviorSolveRational( + c_List[c_MixedBehaviorProfile[T]] LcpBehaviorSolve[T]( c_Game, int p_stopAfter, int p_maxDepth ) except +RuntimeError -cdef extern from "solvers/lp/nfglp.h": - c_List[c_MixedStrategyProfileDouble] LpStrategySolveDouble(c_Game) except +RuntimeError - c_List[c_MixedStrategyProfileRational] LpStrategySolveRational(c_Game) except +RuntimeError - -cdef extern from "solvers/lp/efglp.h": - c_List[c_MixedBehaviorProfileDouble] LpBehaviorSolveDouble(c_Game) except +RuntimeError - c_List[c_MixedBehaviorProfileRational] 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_MixedStrategyProfileDouble] LiapStrategySolve( - c_MixedStrategyProfileDouble, double p_maxregret, int p_maxitsN + c_List[c_MixedStrategyProfile[double]] LiapStrategySolve( + c_MixedStrategyProfile[double], double p_maxregret, int p_maxitsN ) except +RuntimeError - c_List[c_MixedBehaviorProfileDouble] LiapBehaviorSolve( - c_MixedBehaviorProfileDouble, double p_maxregret, int p_maxitsN + c_List[c_MixedBehaviorProfile[double]] LiapBehaviorSolve( + c_MixedBehaviorProfile[double], double p_maxregret, int p_maxitsN ) except +RuntimeError cdef extern from "solvers/simpdiv/simpdiv.h": - c_List[c_MixedStrategyProfileRational] SimpdivStrategySolve( - c_MixedStrategyProfileRational start, c_Rational p_maxregret, int p_gridResize, + c_List[c_MixedStrategyProfile[c_Rational]] SimpdivStrategySolve( + c_MixedStrategyProfile[c_Rational] start, c_Rational p_maxregret, int p_gridResize, int p_leashLength ) except +RuntimeError cdef extern from "solvers/ipa/ipa.h": - c_List[c_MixedStrategyProfileDouble] IPAStrategySolve( - c_MixedStrategyProfileDouble + c_List[c_MixedStrategyProfile[double]] IPAStrategySolve( + c_MixedStrategyProfile[double] ) except +RuntimeError cdef extern from "solvers/gnm/gnm.h": - c_List[c_MixedStrategyProfileDouble] GNMStrategySolve( - c_MixedStrategyProfileDouble, double p_endLambda, int p_steps, + c_List[c_MixedStrategyProfile[double]] GNMStrategySolve( + c_MixedStrategyProfile[double], double p_endLambda, int p_steps, int p_localNewtonInterval, int p_localNewtonMaxits ) except +RuntimeError -cdef extern from "solvers/logit/nfglogit.h": - c_List[c_MixedStrategyProfileDouble] LogitStrategySolve(c_Game, - double, - double, - double) except +RuntimeError +cdef extern from "solvers/nashsupport/nashsupport.h": + cdef cppclass c_PossibleNashStrategySupportsResult "PossibleNashStrategySupportsResult": + stdlist[c_StrategySupportProfile] m_supports + shared_ptr[c_PossibleNashStrategySupportsResult] PossibleNashStrategySupports( + c_Game + ) except +RuntimeError + +cdef extern from "solvers/enumpoly/enumpoly.h": + c_List[c_MixedStrategyProfile[double]] EnumPolyStrategySolve( + c_Game, int, float + ) except +RuntimeError + c_List[c_MixedBehaviorProfile[double]] EnumPolyBehaviorSolve( + c_Game, int, float + ) except +RuntimeError -cdef extern from "solvers/logit/efglogit.h": - c_List[c_MixedBehaviorProfileDouble] LogitBehaviorSolve(c_Game, - double, - double, - double) except +RuntimeError +cdef extern from "solvers/logit/logit.h": + cdef cppclass c_LogitQREMixedBehaviorProfile "LogitQREMixedBehaviorProfile": + c_LogitQREMixedBehaviorProfile(c_Game) except + + c_LogitQREMixedBehaviorProfile(c_LogitQREMixedBehaviorProfile) except + + c_Game GetGame() except + + c_MixedBehaviorProfile[double] GetProfile() # except + doesn't compile + double GetLambda() except + + double GetLogLike() except + + int size() except + + double getitem "operator[]"(int) except +IndexError -cdef extern from "solvers/logit/nfglogit.h": cdef cppclass c_LogitQREMixedStrategyProfile "LogitQREMixedStrategyProfile": c_LogitQREMixedStrategyProfile(c_Game) except + c_LogitQREMixedStrategyProfile(c_LogitQREMixedStrategyProfile) except + c_Game GetGame() except + - c_MixedStrategyProfileDouble GetProfile() # except + doesn't compile + c_MixedStrategyProfile[double] GetProfile() # except + doesn't compile double GetLambda() except + double GetLogLike() except + - int MixedProfileLength() except + + int size() except + double getitem "operator[]"(int) except +IndexError - cdef cppclass c_StrategicQREEstimator "StrategicQREEstimator": - c_StrategicQREEstimator() except + - c_LogitQREMixedStrategyProfile Estimate(c_LogitQREMixedStrategyProfile, - c_MixedStrategyProfileDouble, - double, double, double) except +RuntimeError cdef extern from "nash.h": - shared_ptr[c_LogitQREMixedStrategyProfile] _logit_estimate "logit_estimate"( - shared_ptr[c_MixedStrategyProfileDouble], double, double + c_List[c_MixedBehaviorProfile[double]] LogitBehaviorSolveWrapper( + c_Game, double, double, double ) except + - shared_ptr[c_LogitQREMixedStrategyProfile] _logit_atlambda "logit_atlambda"( + c_List[c_LogitQREMixedBehaviorProfile] LogitBehaviorPrincipalBranchWrapper( c_Game, double, double, double ) except + - c_List[c_LogitQREMixedStrategyProfile] _logit_principal_branch "logit_principal_branch"( + stdlist[shared_ptr[c_LogitQREMixedBehaviorProfile]] LogitBehaviorAtLambdaWrapper( + c_Game, stdlist[double], double, double + ) except + + shared_ptr[c_LogitQREMixedBehaviorProfile] LogitBehaviorEstimateWrapper( + shared_ptr[c_MixedBehaviorProfile[double]], bool, double, double + ) except + + c_List[c_MixedStrategyProfile[double]] LogitStrategySolveWrapper( + c_Game, double, double, double + ) except + + c_List[c_LogitQREMixedStrategyProfile] LogitStrategyPrincipalBranchWrapper( c_Game, double, double, double ) except + + stdlist[shared_ptr[c_LogitQREMixedStrategyProfile]] LogitStrategyAtLambdaWrapper( + c_Game, stdlist[double], double, double + ) except + + shared_ptr[c_LogitQREMixedStrategyProfile] LogitStrategyEstimateWrapper( + shared_ptr[c_MixedStrategyProfile[double]], bool, double, double + ) except + diff --git a/src/pygambit/gambit.pyx b/src/pygambit/gambit.pyx index f45b4358f..95799a33d 100644 --- a/src/pygambit/gambit.pyx +++ b/src/pygambit/gambit.pyx @@ -50,7 +50,7 @@ class Rational(fractions.Fraction): @cython.cfunc def rat_to_py(r: c_Rational): """Convert a C++ Rational number to a Python Rational.""" - return Rational(rat_str(r).decode("ascii")) + return Rational(to_string(r).decode("ascii")) @cython.cfunc diff --git a/src/pygambit/game.pxi b/src/pygambit/game.pxi index 4f4c7fc74..3a2b8968e 100644 --- a/src/pygambit/game.pxi +++ b/src/pygambit/game.pxi @@ -19,15 +19,213 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # +import io import itertools import pathlib import numpy as np import scipy.stats -import pygambit.gte import pygambit.gameiter +ctypedef string (*GameWriter)(const c_Game &) except +IOError +ctypedef c_Game (*GameParser)(const string &) except +IOError + + +@cython.cfunc +def read_game(filepath_or_buffer: typing.Union[str, pathlib.Path, io.IOBase], + parser: GameParser): + + g = cython.declare(Game) + if isinstance(filepath_or_buffer, io.TextIOBase): + data = filepath_or_buffer.read().encode("utf-8") + elif isinstance(filepath_or_buffer, io.IOBase): + data = filepath_or_buffer.read() + else: + with open(filepath_or_buffer, "rb") as f: + data = f.read() + try: + g = Game.wrap(parser(data)) + except Exception as exc: + raise ValueError(f"Parse error in game file: {exc}") from None + return g + + +def read_gbt(filepath_or_buffer: typing.Union[str, pathlib.Path, io.IOBase]) -> Game: + """Construct a game from its serialised representation in a GBT file. + + Parameters + ---------- + filepath_or_buffer : str, pathlib.Path or io.IOBase + The path to the file containing the game representation or file-like object + + Returns + ------- + Game + A game constructed from the representation in the file. + + Raises + ------ + IOError + If the file cannot be opened or read + ValueError + If the contents of the file are not a valid game representation. + + See Also + -------- + read_efg, read_nfg, read_agg + """ + return read_game(filepath_or_buffer, parser=ParseGbtGame) + + +def read_efg(filepath_or_buffer: typing.Union[str, pathlib.Path, io.IOBase]) -> Game: + """Construct a game from its serialised representation in an EFG file. + + Parameters + ---------- + filepath_or_buffer : str, pathlib.Path or io.IOBase + The path to the file containing the game representation or file-like object + + Returns + ------- + Game + A game constructed from the representation in the file. + + Raises + ------ + IOError + If the file cannot be opened or read + ValueError + If the contents of the file are not a valid game representation. + + See Also + -------- + read_gbt, read_nfg, read_agg + """ + return read_game(filepath_or_buffer, parser=ParseEfgGame) + + +def read_nfg(filepath_or_buffer: typing.Union[str, pathlib.Path, io.IOBase]) -> Game: + """Construct a game from its serialised representation in a NFG file. + + Parameters + ---------- + filepath_or_buffer : str, pathlib.Path or io.IOBase + The path to the file containing the game representation or file-like object + + Returns + ------- + Game + A game constructed from the representation in the file. + + Raises + ------ + IOError + If the file cannot be opened or read + ValueError + If the contents of the file are not a valid game representation. + + See Also + -------- + read_gbt, read_efg, read_agg + """ + return read_game(filepath_or_buffer, parser=ParseNfgGame) + + +def read_agg(filepath_or_buffer: typing.Union[str, pathlib.Path, io.IOBase]) -> Game: + """Construct a game from its serialised representation in an AGG file. + + Parameters + ---------- + filepath_or_buffer : str, pathlib.Path or io.IOBase + The path to the file containing the game representation or file-like object + + Returns + ------- + Game + A game constructed from the representation in the file. + + Raises + ------ + IOError + If the file cannot be opened or read + ValueError + If the contents of the file are not a valid game representation. + + See Also + -------- + read_gbt, read_efg, read_nfg + """ + 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]: + """Iterate over the game nodes in the depth-first traversal order.""" + if not self.game.deref().IsTree(): + return + + for node in self.game.deref().GetNodes(): + yield Node.wrap(node) + + +@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: @@ -49,11 +247,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): @@ -93,8 +291,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): @@ -220,7 +418,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) @@ -317,14 +515,7 @@ class Game: Game The newly-created strategic game. """ - cdef Array[int] *d - d = new Array[int](len(dim)) - try: - for i in range(1, len(dim)+1): - setitem_array_int(d, i, dim[i-1]) - g = Game.wrap(NewTable(d)) - finally: - del d + g = Game.wrap(NewTable(list(dim))) g.title = title return g @@ -356,6 +547,7 @@ class Game: See Also -------- from_dict : Create strategic game and set player labels + to_array: Generate the payoff tables for players represented as numpy arrays """ arrays = [np.array(a) for a in arrays] if len(set(a.shape for a in arrays)) > 1: @@ -368,6 +560,39 @@ class Game: g.title = title return g + def to_arrays(self, dtype: typing.Type = Rational) -> typing.List[np.array]: + """Generate the payoff tables for players represented as numpy arrays. + + Parameters + ---------- + dtype : type + The type to which payoff values will be converted and + the resulting arrays will be of that dtype + + Returns + ------- + list of np.array + + See Also + -------- + from_arrays : Create game from list-like of array-like + """ + arrays = [] + + shape = tuple(len(player.strategies) for player in self.players) + for player in self.players: + array = np.zeros(shape=shape, dtype=object) + for profile in itertools.product(*(range(s) for s in shape)): + try: + array[profile] = dtype(self[profile][player]) + except (ValueError, TypeError, IndexError, KeyError): + raise ValueError( + f"Payoff '{self[profile][player]}' cannot be " + f"converted to requested type '{dtype}'" + ) from None + arrays.append(array) + return arrays + @classmethod def from_dict(cls, payoffs, title: str = "Untitled strategic game") -> Game: """Create a new ``Game`` with a strategic representation. @@ -409,66 +634,6 @@ class Game: g.title = title return g - @classmethod - def read_game(cls, filepath: typing.Union[str, pathlib.Path]) -> Game: - """Construct a game from its serialised representation in a file. - - Parameters - ---------- - filepath : str or path object - The path to the file containing the game representation. - - Returns - ------- - Game - A game constructed from the representation in the file. - - Raises - ------ - IOError - If the file cannot be opened or read - ValueError - If the contents of the file are not a valid game representation. - - See Also - -------- - parse_game : Constructs a game from a text string. - """ - with open(filepath, "rb") as f: - data = f.read() - try: - return Game.wrap(ParseGame(data)) - except Exception as exc: - raise ValueError(f"Parse error in game file: {exc}") from None - - @classmethod - def parse_game(cls, text: str) -> Game: - """Construct a game from its serialised representation in a string - - Parameters - ---------- - text : str - A string containing the game representation. - - Returns - ------- - Game - A game constructed from the representation in the string. - - Raises - ------ - ValueError - If the contents of the file are not a valid game representation. - - See Also - -------- - read_game : Constructs a game from a representation in a file. - """ - try: - return Game.wrap(ParseGame(text.encode("ascii"))) - except Exception as exc: - raise ValueError(f"Parse error in game file: {exc}") from None - def __repr__(self) -> str: if self.title: return f"Game(title='{self.title}')" @@ -479,7 +644,7 @@ class Game: if self.is_tree: return repr(self) else: - return self.write("html") + return self.to_html() def __eq__(self, other: typing.Any) -> bool: return ( @@ -566,6 +731,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.""" @@ -603,12 +789,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`. @@ -658,14 +844,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: @@ -747,12 +933,14 @@ class Game: "Mixed strategies not supported for games with imperfect recall." ) if rational: - mspr = MixedStrategyProfileRational.wrap(make_shared[c_MixedStrategyProfileRational]( - self.game.deref().NewMixedStrategyProfile(c_Rational()) - )) + mspr = MixedStrategyProfileRational.wrap( + make_shared[c_MixedStrategyProfile[c_Rational]]( + self.game.deref().NewMixedStrategyProfile(c_Rational()) + ) + ) return self._fill_strategy_profile(mspr, data, Rational) else: - mspd = MixedStrategyProfileDouble.wrap(make_shared[c_MixedStrategyProfileDouble]( + mspd = MixedStrategyProfileDouble.wrap(make_shared[c_MixedStrategyProfile[double]]( self.game.deref().NewMixedStrategyProfile(0.0) )) return self._fill_strategy_profile(mspd, data, float) @@ -866,12 +1054,12 @@ class Game: ) if rational: mbpr = MixedBehaviorProfileRational.wrap( - make_shared[c_MixedBehaviorProfileRational](self.game) + make_shared[c_MixedBehaviorProfile[c_Rational]](self.game) ) return self._fill_behavior_profile(mbpr, data, Rational) else: mbpd = MixedBehaviorProfileDouble.wrap( - make_shared[c_MixedBehaviorProfileDouble](self.game) + make_shared[c_MixedBehaviorProfile[double]](self.game) ) return self._fill_behavior_profile(mbpd, data, float) @@ -934,80 +1122,149 @@ class Game: profile[action] = Rational(hi - lo - 1, denom) return profile - def support_profile(self): - return StrategySupportProfile.wrap(self) + def strategy_support_profile( + self, strategies: typing.Callable | None = None + ) -> StrategySupportProfile: + """Create a new `StrategySupportProfile` on the game. - 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. + Parameters + ---------- + strategies : function, optional + By default the support profile contains all strategies for all players. + If specified, only strategies for which the supplied function returns `True` + are included. + + Returns + ------- + StrategySupportProfile + """ + profile = StrategySupportProfile.wrap(make_shared[c_StrategySupportProfile](self.game)) + if strategies is not None: + for strategy in self.strategies: + if not strategies(strategy): + if not (deref(profile.profile) + .RemoveStrategy(cython.cast(Strategy, strategy).strategy)): + raise ValueError("attempted to remove the last strategy for player") + return profile + + @cython.cfunc + def _to_format( + self, + writer: GameWriter, + filepath_or_buffer: typing.Union[str, pathlib.Path, io.IOBase, None] = None + ): + serialized_game = writer(self.game) + if filepath_or_buffer is None: + return serialized_game.decode() + if isinstance(filepath_or_buffer, io.TextIOBase): + filepath_or_buffer.write(serialized_game.decode()) + elif isinstance(filepath_or_buffer, io.IOBase): + filepath_or_buffer.write(serialized_game) + else: + with open(filepath_or_buffer, "w") as f: + f.write(serialized_game.decode()) - 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. + def to_efg( + self, + filepath_or_buffer: typing.Union[str, pathlib.Path, io.IOBase, None] = None + ) -> typing.Union[str, None]: + """Save the game to an .efg file or return its serialized representation Parameters ---------- - subtree : Node or str, optional - If specified, return only the nodes in the subtree rooted at `subtree`. + filepath_or_buffer : str or Path or io.IOBase or None, default None + String, path object, or file-like object implementing a write() function. + If None, the result is returned as a string. - Raises + Return ------ - MismatchError - If `node` is a `Node` from a different game. + String representation of the game or None if the game is saved to a file + + See Also + -------- + to_nfg, to_html, to_latex """ - 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)] - ) + return self._to_format(WriteEfgFile, filepath_or_buffer) + + def to_nfg( + self, + filepath_or_buffer: typing.Union[str, pathlib.Path, io.IOBase, None] = None + ) -> typing.Union[str, None]: + """Save the game to a .nfg file or return its serialized representation + + Parameters + ---------- + filepath_or_buffer : str or Path or BufferedWriter or None, default None + String, path object, or file-like object implementing a write() function. + If None, the result is returned as a string. + + Return + ------ + String representation of the game or None if the game is saved to a file + + See Also + -------- + to_efg, to_html, to_latex + """ + return self._to_format(WriteNfgFile, filepath_or_buffer) + + def to_html( + self, + filepath_or_buffer: typing.Union[str, pathlib.Path, io.IOBase, None] = None + ) -> typing.Union[str, None]: + """Export the game to HTML format. + + Generates a rendering of the strategic form of the game as a + collection of HTML tables. The first player is the row + chooser; the second player the column chooser. For games with + more than two players, a collection of tables is generated, + one for each possible strategy combination of players 3 and higher. + + Parameters + ---------- + filepath_or_buffer : str or Path or BufferedWriter or None, default None + String, path object, or file-like object implementing a write() function. + If None, the result is returned as a string. + + Return + ------ + String representation of the game or None if the game is exported to a file + + See Also + -------- + to_efg, to_nfg, to_latex + """ + return self._to_format(WriteHTMLFile, filepath_or_buffer) + + def to_latex( + self, + filepath_or_buffer: typing.Union[str, pathlib.Path, io.IOBase, None] = None + ) -> typing.Union[str, None]: + """Export the game to LaTeX format. + + Generates a rendering of the strategic form of the game in + LaTeX, suitable for use with `Martin Osborne's sgame style + `_. + The first player is the row + chooser; the second player the column chooser. For games with + more than two players, a collection of tables is generated, + one for each possible strategy combination of players 3 and higher. + + Parameters + ---------- + filepath_or_buffer : str or Path or BufferedWriter or None, default None + String, path object, or file-like object implementing a write() function. + If None, the result is returned as a string. - def write(self, format="native") -> str: - """Produce a serialization of the game. - - Several output formats are - supported, depending on the representation of the game. - - * `efg`: A representation of the game in - :ref:`the .efg extensive game file format `. - Not available for games in strategic representation. - * `nfg`: A representation of the game in - :ref:`the .nfg strategic game file format `. - For an extensive game, this uses the reduced strategic form - representation. - * `gte`: The XML representation used by the Game Theory Explorer - tool. Only available for extensive games. - * `native`: The format most appropriate to the - underlying representation of the game, i.e., `efg` or `nfg`. - - This method also supports exporting to other output formats - (which cannot be used directly to re-load the game later, but - are suitable for human consumption, inclusion in papers, and so - on). - - * `html`: A rendering of the strategic form of the game as a - collection of HTML tables. The first player is the row - chooser; the second player the column chooser. For games with - more than two players, a collection of tables is generated, - one for each possible strategy combination of players 3 and higher. - * `sgame`: A rendering of the strategic form of the game in - LaTeX, suitable for use with `Martin Osborne's sgame style - `_. - The first player is the row - chooser; the second player the column chooser. For games with - more than two players, a collection of tables is generated, - one for each possible strategy combination of players 3 and higher. + Return + ------ + String representation of the game or None if the game is exported to a file + + See Also + -------- + to_efg, to_nfg, to_html """ - if format == "gte": - return pygambit.gte.write_game(self) - else: - return WriteGame(self.game, format.encode("ascii")).decode("ascii") + return self._to_format(WriteLaTeXFile, filepath_or_buffer) def _resolve_player(self, player: typing.Any, funcname: str, argname: str = "player") -> Player: @@ -1166,7 +1423,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}'") @@ -1231,7 +1488,7 @@ class Game: except KeyError: raise KeyError(f"{funcname}(): no information set with label '{infoset}'") raise TypeError( - f"{funcname}(): {argname} must be Infoset or str, not {node.__class__.__name__}" + f"{funcname}(): {argname} must be Infoset or str, not {infoset.__class__.__name__}" ) def _resolve_action(self, @@ -1299,12 +1556,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: @@ -1325,7 +1582,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: @@ -1344,7 +1601,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: @@ -1359,7 +1616,7 @@ 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 the node `src` to the node `dest`. @@ -1391,7 +1648,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_dest.node.deref().CopyTree(resolved_src.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'. @@ -1416,7 +1673,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_dest.node.deref().MoveTree(resolved_src.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 @@ -1435,7 +1692,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. @@ -1452,7 +1709,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], @@ -1476,14 +1733,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 @@ -1504,7 +1762,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 @@ -1516,7 +1774,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], @@ -1543,7 +1801,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], @@ -1572,7 +1830,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. @@ -1611,7 +1869,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, @@ -1689,10 +1947,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`. @@ -1722,10 +1980,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. @@ -1752,4 +2010,4 @@ class Game: ) if len(resolved_strategy.player.strategies) == 1: raise UndefinedOperationError("Cannot delete the only strategy for a player") - resolved_strategy.strategy.deref().DeleteStrategy() + self.game.deref().DeleteStrategy(resolved_strategy.strategy) diff --git a/src/pygambit/gte.py b/src/pygambit/gte.py deleted file mode 100644 index 4a2836ef9..000000000 --- a/src/pygambit/gte.py +++ /dev/null @@ -1,130 +0,0 @@ -# -# This file is part of Gambit -# Copyright (c) 1994-2025, The Gambit Project (https://www.gambit-project.org) -# -# FILE: src/python/gambit/gte.py -# File format interface with Game Theory Explorer -# -# 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. -# - -""" -File format interface with Game Theory Explorer -""" - -from fractions import Fraction - -from lxml import etree - -import pygambit - - -def read_game_subtree(game, node, xml_node): - if xml_node.get("player") is None: - node.append_move(game.players.chance, len(xml_node)) - elif xml_node.get("iset") is not None: - p = game.players[int(xml_node.get("player"))-1] - try: - node.append_move(p.infosets[xml_node.get("iset")]) - except IndexError: - iset = node.append_move(p, len(xml_node)) - iset.label = xml_node.get("iset") - elif xml_node.get("player") is not None: - node.append_move(game.players[int(xml_node.get("player"))-1], - len(xml_node)) - for (i, xml_child) in enumerate(xml_node): - if xml_child.get("prob") is not None: - node.infoset.actions[i].prob = Fraction(xml_child.get("prob")) - if xml_child.get("move") is not None: - node.infoset.actions[i].label = xml_child.get("move") - if xml_child.tag == "outcome": - node.children[i].outcome = game.outcomes.add() - for xml_payoff in xml_child.xpath("./payoff"): - node.children[i].outcome[int(xml_payoff.get("player"))-1] = ( - Fraction(xml_payoff.text) - ) - elif xml_child.tag == "node": - read_game_subtree(game, node.children[i], xml_child) - - -def read_game(f): - tree = etree.parse(f) - if tree.xpath("/gte/@version")[0] != "0.1": - raise ValueError("GTE reader only supports version 0.1") - - g = pygambit.Game.new_tree() - for p in tree.xpath("/gte/players/player"): - g.players.add(p.text) - - read_game_subtree(g, g.root, tree.xpath("/gte/extensiveForm/node")[0]) - return g - - -def write_game_outcome(game, outcome, doc, xml_parent): - for p in game.players: - if outcome is not None: - etree.SubElement(xml_parent, "payoff", - player=p.label).text = str(outcome[p]) - else: - etree.SubElement(xml_parent, "payoff", - player=p.label).text = "0" - - -def write_game_node(game, node, doc, xml_node): - if len(node.infoset.members) >= 2: - xml_node.set("iset", node.infoset.label) - if not node.infoset.is_chance: - xml_node.set("player", node.player.label) - for (i, child) in enumerate(node.children): - if child.is_terminal: - xml_child = etree.SubElement(xml_node, "outcome") - write_game_outcome(game, child.outcome, doc, xml_child) - else: - xml_child = etree.SubElement(xml_node, "node") - write_game_node(game, child, doc, xml_child) - if node.infoset.is_chance: - xml_child.set("prob", str(node.infoset.actions[i].prob)) - xml_child.set("move", node.infoset.actions[i].label) - - -def write_game_display(game, doc, xml_display): - for i in range(len(game.players)): - color = etree.SubElement(xml_display, "color", player=str(i+1)) - if i % 2 == 0: - color.text = "#FF0000" - else: - color.text = "#0000FF" - etree.SubElement(xml_display, "font").text = "Times" - etree.SubElement(xml_display, "strokeWidth").text = "1" - etree.SubElement(xml_display, "nodeDiameter").text = "7" - etree.SubElement(xml_display, "isetDiameter").text = "25" - etree.SubElement(xml_display, "levelDistance").text = "75" - - -def write_game(game): - gte = etree.Element("gte", version="0.1") - doc = etree.ElementTree(gte) - etree.SubElement(gte, "gameDescription") - display = etree.SubElement(gte, "display") - write_game_display(game, doc, display) - players = etree.SubElement(gte, "players") - for (i, p) in enumerate(game.players): - etree.SubElement(players, "player", - playerId=str(i+1)).text = p.label - efg = etree.SubElement(gte, "extensiveForm") - xml_root = etree.SubElement(efg, "node") - write_game_node(game, game.root, doc, xml_root) - - return etree.tostring(doc, pretty_print=True, encoding="unicode") 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.h b/src/pygambit/nash.h index 2d1381f4a..a6b3755f6 100644 --- a/src/pygambit/nash.h +++ b/src/pygambit/nash.h @@ -21,52 +21,87 @@ // #include "gambit.h" -#include "solvers/logit/nfglogit.h" +#include "solvers/logit/logit.h" using namespace std; using namespace Gambit; -class NullBuffer : public std::streambuf { -public: - int overflow(int c) { return c; } -}; +List> LogitBehaviorSolveWrapper(const Game &p_game, double p_regret, + double p_firstStep, double p_maxAccel) +{ + List> ret; + ret.push_back(LogitBehaviorSolve(LogitQREMixedBehaviorProfile(p_game), p_regret, 1.0, + p_firstStep, p_maxAccel) + .back() + .GetProfile()); + return ret; +} -std::shared_ptr -logit_estimate(std::shared_ptr> p_frequencies, double p_firstStep, - double p_maxAccel) +inline List LogitBehaviorPrincipalBranchWrapper(const Game &p_game, + double p_regret, + double p_firstStep, + double p_maxAccel) +{ + return LogitBehaviorSolve(LogitQREMixedBehaviorProfile(p_game), p_regret, 1.0, p_firstStep, + p_maxAccel); +} + +std::shared_ptr +LogitBehaviorEstimateWrapper(std::shared_ptr> p_frequencies, + bool p_stopAtLocal, double p_firstStep, double p_maxAccel) +{ + return make_shared(LogitBehaviorEstimate( + *p_frequencies, 1000000.0, 1.0, p_stopAtLocal, p_firstStep, p_maxAccel)); +} + +std::list> +LogitBehaviorAtLambdaWrapper(const Game &p_game, const std::list &p_targetLambda, + double p_firstStep, double p_maxAccel) { - LogitQREMixedStrategyProfile start(p_frequencies->GetGame()); - StrategicQREEstimator alg; - alg.SetMaxDecel(p_maxAccel); - alg.SetStepsize(p_firstStep); - NullBuffer null_buffer; - std::ostream null_stream(&null_buffer); - LogitQREMixedStrategyProfile result = - alg.Estimate(start, *p_frequencies, null_stream, 1000000.0, 1.0); - return make_shared(result); + LogitQREMixedBehaviorProfile start(p_game); + std::list> ret; + for (auto &qre : LogitBehaviorSolveLambda(start, p_targetLambda, 1.0, p_firstStep, p_maxAccel)) { + ret.push_back(std::make_shared(qre)); + } + return ret; } -std::shared_ptr logit_atlambda(const Game &p_game, double p_lambda, +List> LogitStrategySolveWrapper(const Game &p_game, double p_regret, double p_firstStep, double p_maxAccel) { - LogitQREMixedStrategyProfile start(p_game); - StrategicQREPathTracer alg; - alg.SetMaxDecel(p_maxAccel); - alg.SetStepsize(p_firstStep); - NullBuffer null_buffer; - std::ostream null_stream(&null_buffer); - return make_shared( - alg.SolveAtLambda(start, null_stream, p_lambda, 1.0)); + List> ret; + ret.push_back(LogitStrategySolve(LogitQREMixedStrategyProfile(p_game), p_regret, 1.0, + p_firstStep, p_maxAccel) + .back() + .GetProfile()); + return ret; +} + +inline List LogitStrategyPrincipalBranchWrapper(const Game &p_game, + double p_regret, + double p_firstStep, + double p_maxAccel) +{ + return LogitStrategySolve(LogitQREMixedStrategyProfile(p_game), p_regret, 1.0, p_firstStep, + p_maxAccel); } -List logit_principal_branch(const Game &p_game, double p_maxregret, - double p_firstStep, double p_maxAccel) +std::list> +LogitStrategyAtLambdaWrapper(const Game &p_game, const std::list &p_targetLambda, + double p_firstStep, double p_maxAccel) { LogitQREMixedStrategyProfile start(p_game); - StrategicQREPathTracer alg; - alg.SetMaxDecel(p_maxAccel); - alg.SetStepsize(p_firstStep); - NullBuffer null_buffer; - std::ostream null_stream(&null_buffer); - return alg.TraceStrategicPath(start, null_stream, p_maxregret, 1.0); + std::list> ret; + for (auto &qre : LogitStrategySolveLambda(start, p_targetLambda, 1.0, p_firstStep, p_maxAccel)) { + ret.push_back(std::make_shared(qre)); + } + return ret; +} + +std::shared_ptr +LogitStrategyEstimateWrapper(std::shared_ptr> p_frequencies, + bool p_stopAtLocal, double p_firstStep, double p_maxAccel) +{ + return make_shared(LogitStrategyEstimate( + *p_frequencies, 1000000.0, 1.0, p_stopAtLocal, p_firstStep, p_maxAccel)); } diff --git a/src/pygambit/nash.pxi b/src/pygambit/nash.pxi index c53958aeb..4ed8e90a2 100644 --- a/src/pygambit/nash.pxi +++ b/src/pygambit/nash.pxi @@ -29,37 +29,37 @@ import typing @cython.cfunc def _convert_mspd( - inlist: c_List[c_MixedStrategyProfileDouble] -) -> typing.List[MixedStrategyProfileDouble]: + inlist: c_List[c_MixedStrategyProfile[float]] +) -> typing.List[MixedStrategyProfile[double]]: return [MixedStrategyProfileDouble.wrap(copyitem_list_mspd(inlist, i+1)) - for i in range(inlist.Length())] + for i in range(inlist.size())] @cython.cfunc def _convert_mspr( - inlist: c_List[c_MixedStrategyProfileRational] -) -> typing.List[MixedStrategyProfileRational]: + inlist: c_List[c_MixedStrategyProfile[c_Rational]] +) -> typing.List[MixedStrategyProfile[c_Rational]]: return [MixedStrategyProfileRational.wrap(copyitem_list_mspr(inlist, i+1)) - for i in range(inlist.Length())] + for i in range(inlist.size())] @cython.cfunc def _convert_mbpd( - inlist: c_List[c_MixedBehaviorProfileDouble] -) -> typing.List[MixedBehaviorProfileDouble]: + inlist: c_List[c_MixedBehaviorProfile[float]] +) -> typing.List[MixedBehaviorProfile[double]]: return [MixedBehaviorProfileDouble.wrap(copyitem_list_mbpd(inlist, i+1)) - for i in range(inlist.Length())] + for i in range(inlist.size())] @cython.cfunc def _convert_mbpr( - inlist: c_List[c_MixedBehaviorProfileRational] -) -> typing.List[MixedBehaviorProfileRational]: + inlist: c_List[c_MixedBehaviorProfile[c_Rational]] +) -> typing.List[MixedBehaviorProfile[c_Rational]]: return [MixedBehaviorProfileRational.wrap(copyitem_list_mbpr(inlist, i+1)) - for i in range(inlist.Length())] + for i in range(inlist.size())] -def _enumpure_strategy_solve(game: Game) -> typing.List[MixedStrategyProfileRational]: +def _enumpure_strategy_solve(game: Game) -> typing.List[MixedStrategyProfile[c_Rational]]: return _convert_mspr(EnumPureStrategySolve(game.game)) @@ -68,55 +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)) - - -def _enummixed_strategy_solve_lrs(game: Game) -> typing.List[MixedStrategyProfileRational]: - return _convert_mspr(EnumMixedStrategySolveLrs(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, @@ -166,16 +162,41 @@ def _gnm_strategy_solve( raise +def _nashsupport_strategy_solve(game: Game) -> typing.List[StrategySupportProfile]: + return [ + StrategySupportProfile.wrap(support) + for support in make_list_of_pointer( + deref(PossibleNashStrategySupports(game.game)).m_supports + ) + ] + + +def _enumpoly_strategy_solve( + game: Game, + stop_after: int, + maxregret: float, +) -> typing.List[MixedStrategyProfileDouble]: + return _convert_mspd(EnumPolyStrategySolve(game.game, stop_after, maxregret)) + + +def _enumpoly_behavior_solve( + game: Game, + stop_after: int, + maxregret: float, +) -> typing.List[MixedBehaviorProfileDouble]: + return _convert_mbpd(EnumPolyBehaviorSolve(game.game, stop_after, maxregret)) + + def _logit_strategy_solve( game: Game, maxregret: float, first_step: float, max_accel: float, ) -> typing.List[MixedStrategyProfileDouble]: - return _convert_mspd(LogitStrategySolve(game.game, maxregret, first_step, max_accel)) + return _convert_mspd(LogitStrategySolveWrapper(game.game, maxregret, first_step, max_accel)) def _logit_behavior_solve( game: Game, maxregret: float, first_step: float, max_accel: float, ) -> typing.List[MixedBehaviorProfileDouble]: - return _convert_mbpd(LogitBehaviorSolve(game.game, maxregret, first_step, max_accel)) + return _convert_mbpd(LogitBehaviorSolveWrapper(game.game, maxregret, first_step, max_accel)) @cython.cclass @@ -198,7 +219,7 @@ class LogitQREMixedStrategyProfile: return "LogitQREMixedStrategyProfile(lam=%f,profile=%s)" % (self.lam, self.profile) def __len__(self): - return deref(self.thisptr).MixedProfileLength() + return deref(self.thisptr).size() def __getitem__(self, int i): return deref(self.thisptr).getitem(i+1) @@ -222,34 +243,131 @@ class LogitQREMixedStrategyProfile: def profile(self) -> MixedStrategyProfileDouble: """The mixed strategy profile.""" return MixedStrategyProfileDouble.wrap( - make_shared[c_MixedStrategyProfileDouble](deref(self.thisptr).GetProfile()) + make_shared[c_MixedStrategyProfile[double]](deref(self.thisptr).GetProfile()) ) -def logit_estimate(profile: MixedStrategyProfileDouble, - first_step: float = .03, - max_accel: float = 1.1) -> LogitQREMixedStrategyProfile: +def _logit_strategy_estimate(profile: MixedStrategyProfileDouble, + local_max: bool = False, + first_step: float = .03, + max_accel: float = 1.1) -> LogitQREMixedStrategyProfile: """Estimate QRE corresponding to mixed strategy profile using maximum likelihood along the principal branch. """ return LogitQREMixedStrategyProfile.wrap( - _logit_estimate(profile.profile, first_step, max_accel) + LogitStrategyEstimateWrapper(profile.profile, local_max, first_step, max_accel) ) -def logit_atlambda(game: Game, - lam: float, - first_step: float = .03, - max_accel: float = 1.1) -> LogitQREMixedStrategyProfile: - """Compute the first QRE along the principal branch with the given - lambda parameter. +def _logit_strategy_lambda(game: Game, + lam: typing.Union[float, typing.List[float]], + first_step: float = .03, + max_accel: float = 1.1) -> typing.List[LogitQREMixedStrategyProfile]: + """Compute the first QRE encountered along the principal branch of the strategic + game corresponding to lambda value `lam`. """ - return LogitQREMixedStrategyProfile.wrap( - _logit_atlambda(game.game, lam, first_step, max_accel) - ) + try: + iter(lam) + except TypeError: + lam = [lam] + return [LogitQREMixedStrategyProfile.wrap(profile) + for profile in LogitStrategyAtLambdaWrapper(game.game, lam, first_step, max_accel)] + + +def _logit_strategy_branch(game: Game, + maxregret: float, + first_step: float, + max_accel: float): + solns = LogitStrategyPrincipalBranchWrapper(game.game, maxregret, first_step, max_accel) + return [LogitQREMixedStrategyProfile.wrap(copyitem_list_qrem(solns, i+1)) + for i in range(solns.size())] -def logit_principal_branch(game: Game, first_step: float = .03, max_accel: float = 1.1): - solns = _logit_principal_branch(game.game, 1.0e-8, first_step, max_accel) - return [LogitQREMixedStrategyProfile.wrap(copyitem_list_qrem(solns, i+1)) - for i in range(solns.Length())] +@cython.cclass +class LogitQREMixedBehaviorProfile: + thisptr = cython.declare(shared_ptr[c_LogitQREMixedBehaviorProfile]) + + def __init__(self, game=None): + if game is not None: + self.thisptr = make_shared[c_LogitQREMixedBehaviorProfile]( + cython.cast(Game, game).game + ) + + def __repr__(self): + return f"LogitQREMixedBehaviorProfile(lam={self.lam},profile={self.profile})" + + def __len__(self): + return deref(self.thisptr).size() + + def __getitem__(self, int i): + return deref(self.thisptr).getitem(i+1) + + @property + def game(self) -> Game: + """The game on which this mixed strategy profile is defined.""" + g = Game() + g.game = deref(self.thisptr).GetGame() + return g + + @property + def lam(self) -> double: + """The value of the precision parameter.""" + return deref(self.thisptr).GetLambda() + + @property + def log_like(self) -> double: + """The log-likelihood of the data.""" + return deref(self.thisptr).GetLogLike() + + @property + def profile(self) -> MixedBehaviorProfileDouble: + """The mixed strategy profile.""" + profile = MixedBehaviorProfileDouble() + profile.profile = ( + make_shared[c_MixedBehaviorProfile[double]](deref(self.thisptr).GetProfile()) + ) + return profile + + +def _logit_behavior_estimate(profile: MixedBehaviorProfileDouble, + local_max: bool = False, + first_step: float = .03, + max_accel: float = 1.1) -> LogitQREMixedBehaviorProfile: + """Estimate QRE corresponding to mixed behavior profile using + maximum likelihood along the principal branch. + """ + ret = LogitQREMixedBehaviorProfile(profile.game) + ret.thisptr = LogitBehaviorEstimateWrapper(profile.profile, local_max, first_step, max_accel) + return ret + + +def _logit_behavior_lambda(game: Game, + lam: typing.Union[float, typing.List[float]], + first_step: float = .03, + max_accel: float = 1.1) -> typing.List[LogitQREMixedBehaviorProfile]: + """Compute the first QRE encountered along the principal branch of the extensive + game corresponding to lambda value `lam`. + """ + try: + iter(lam) + except TypeError: + lam = [lam] + ret = [] + for profile in LogitBehaviorAtLambdaWrapper(game.game, lam, first_step, max_accel): + qre = LogitQREMixedBehaviorProfile() + qre.thisptr = profile + ret.append(qre) + return ret + + +def _logit_behavior_branch(game: Game, + maxregret: float, + first_step: float, + max_accel: float): + solns = LogitBehaviorPrincipalBranchWrapper(game.game, maxregret, first_step, max_accel) + ret = [] + for i in range(solns.size()): + p = LogitQREMixedBehaviorProfile() + p.thisptr = copyitem_list_qreb(solns, i+1) + ret.append(p) + return ret diff --git a/src/pygambit/nash.py b/src/pygambit/nash.py index 5564add9a..6579d11db 100644 --- a/src/pygambit/nash.py +++ b/src/pygambit/nash.py @@ -23,13 +23,17 @@ A set of utilities for computing Nash equilibria """ +from __future__ import annotations + import dataclasses -import typing +import pathlib import pygambit.gambit as libgbt -MixedStrategyEquilibriumSet = typing.List[libgbt.MixedStrategyProfile] -MixedBehaviorEquilibriumSet = typing.List[libgbt.MixedBehaviorProfile] +from . import nashlrs, nashphc + +MixedStrategyEquilibriumSet = list[libgbt.MixedStrategyProfile] +MixedBehaviorEquilibriumSet = list[libgbt.MixedBehaviorProfile] @dataclasses.dataclass(frozen=True) @@ -57,7 +61,7 @@ class NashComputationResult: method: str rational: bool use_strategic: bool - equilibria: typing.Union[MixedStrategyEquilibriumSet, MixedBehaviorEquilibriumSet] + equilibria: MixedStrategyEquilibriumSet | MixedBehaviorEquilibriumSet parameters: dict = dataclasses.field(default_factory=dict) @@ -99,7 +103,7 @@ def enumpure_solve(game: libgbt.Game, use_strategic: bool = True) -> NashComputa def enummixed_solve( game: libgbt.Game, rational: bool = True, - use_lrs: bool = False + lrsnash_path: pathlib.Path | str | None = None, ) -> NashComputationResult: """Compute all :ref:`mixed-strategy Nash equilibria ` of a two-player game using the strategic representation. @@ -108,11 +112,16 @@ def enummixed_solve( ---------- game : Game The game to compute equilibria in. + rational : bool, default True Compute using rational numbers. If `False`, using floating-point arithmetic. Using rationals is more precise, but slower. - use_lrs : bool, default False - If `True`, use the implementation based on ``lrslib``. This is experimental. + + lrsnash_path : pathlib.Path | str | None = None, + If specified, use lrsnash to solve the systems of equations. + This argument specifies the path to the lrsnash executable. + + .. versionadded:: 16.3.0 Returns ------- @@ -123,17 +132,29 @@ def enummixed_solve( ------ RuntimeError If game has more than two players. + + Notes + ----- + `lrsnash` is part of `lrslib`, available at http://cgm.cs.mcgill.ca/~avis/C/lrs.html """ - if use_lrs: - equilibria = libgbt._enummixed_strategy_solve_lrs(game) - elif rational: + if lrsnash_path is not None: + equilibria = nashlrs.lrsnash_solve(game, lrsnash_path=lrsnash_path) + return NashComputationResult( + game=game, + method="enummixed", + rational=True, + use_strategic=True, + parameters={"lrsnash_path": lrsnash_path}, + equilibria=equilibria, + ) + if rational: equilibria = libgbt._enummixed_strategy_solve_rational(game) else: equilibria = libgbt._enummixed_strategy_solve_double(game) return NashComputationResult( game=game, method="enummixed", - rational=use_lrs or rational, + rational=rational, use_strategic=True, equilibria=equilibria ) @@ -143,8 +164,8 @@ def lcp_solve( game: libgbt.Game, rational: bool = True, use_strategic: bool = False, - stop_after: typing.Optional[int] = None, - max_depth: typing.Optional[int] = None + stop_after: int | None = None, + max_depth: int | None = None ) -> NashComputationResult: """Compute Nash equilibria of a two-player game using :ref:`linear complementarity programming `. @@ -153,15 +174,19 @@ def lcp_solve( ---------- game : Game The game to compute equilibria in. + rational : bool, default True Compute using rational numbers. If `False`, using floating-point arithmetic. Using rationals is more precise, but slower. + use_strategic : bool, default False Whether to use the strategic form. If `True`, always uses the strategic representation even if the game's native representation is extensive. + stop_after : int, optional Maximum number of equilibria to compute. If not specified, computes all accessible equilibria. + max_depth : int, optional Maximum depth of recursion. If specified, will limit the recursive search, but may result in some accessible equilibria not being found. @@ -178,6 +203,10 @@ def lcp_solve( """ if stop_after is None: stop_after = 0 + elif stop_after < 0: + raise ValueError( + f"lcp_solve(): stop_after argument must be a non-negative number; got {stop_after}" + ) if max_depth is None: max_depth = 0 if not game.is_tree or use_strategic: @@ -211,9 +240,11 @@ def lp_solve( ---------- game : Game The game to compute equilibria in. + rational : bool, default True Compute using rational numbers. If `False`, using floating-point arithmetic. Using rationals is more precise, but slower. + use_strategic : bool, default False Whether to use the strategic form. If `True`, always uses the strategic representation even if the game's native representation is extensive. @@ -247,8 +278,7 @@ def lp_solve( def liap_solve( - start: typing.Union[libgbt.MixedStrategyProfileDouble, - libgbt.MixedBehaviorProfileDouble], + start: libgbt.MixedStrategyProfileDouble | libgbt.MixedBehaviorProfileDouble, maxregret: float = 1.0e-4, maxiter: int = 1000 ) -> NashComputationResult: @@ -310,9 +340,9 @@ def liap_solve( def simpdiv_solve( start: libgbt.MixedStrategyProfileRational, - maxregret: libgbt.Rational = None, + maxregret: libgbt.Rational | None = None, refine: int = 2, - leash: typing.Optional[int] = None + leash: int | None = None ) -> NashComputationResult: """Compute Nash equilibria of a game using :ref:`simplicial subdivision `. @@ -371,7 +401,7 @@ def simpdiv_solve( def ipa_solve( - perturbation: typing.Union[libgbt.Game, libgbt.MixedStrategyProfileDouble] + perturbation: libgbt.Game | libgbt.MixedStrategyProfileDouble, ) -> NashComputationResult: """Compute Nash equilibria of a game using :ref:`iterated polymatrix approximation `. @@ -423,7 +453,7 @@ def ipa_solve( def gnm_solve( - perturbation: typing.Union[libgbt.Game, libgbt.MixedStrategyProfileDouble], + perturbation: libgbt.Game | libgbt.MixedStrategyProfileDouble, end_lambda: float = -10.0, steps: int = 100, local_newton_interval: int = 3, @@ -527,6 +557,112 @@ def gnm_solve( raise +def possible_nash_supports(game: libgbt.Game) -> list[libgbt.StrategySupportProfile]: + """Compute the set of support profiles which could possibly form the support + of a totally-mixed Nash equilibrium. + + Warnings + -------- + This implementation is currently experimental. + + Parameters + ---------- + game : Game + The game to compute the supports in. + + Returns + ------- + res : list of StrategySupportProfile + The list of computed support profiles + """ + return libgbt._nashsupport_strategy_solve(game) + + +def enumpoly_solve( + game: libgbt.Game, + use_strategic: bool = False, + stop_after: int | None = None, + maxregret: float = 1.0e-4, + phcpack_path: pathlib.Path | str | None = None +) -> NashComputationResult: + """Compute Nash equilibria by enumerating all support profiles of strategies + or actions, and for each support finding all totally-mixed equilibria of + the game over that support. + + Parameters + ---------- + game : Game + The game to compute equilibria in. + + use_strategic : bool, default False + Whether to use the strategic form. If `True`, always uses the strategic + representation even if the game's native representation is extensive. + + stop_after : int, optional + Maximum number of equilibria to compute. If not specified, examines + all support profiles of the game. + + maxregret : float, default 1e-4 + The acceptance criterion for approximate Nash equilibrium; the maximum + regret of any player must be no more than `maxregret` times the + difference of the maximum and minimum payoffs of the game + + phcpack_path : str or pathlib.Path, optional + If specified, use PHCpack to solve the systems of equations. + This argument specifies the path to the PHCpack executable. + With this method, only enumeration on the strategic game is supported. + + Returns + ------- + res : NashComputationResult + The result represented as a ``NashComputationResult`` object. + + Notes + ----- + PHCpack is available at https://homepages.math.uic.edu/~jan/PHCpack/phcpack.html + """ + if stop_after is None: + stop_after = 0 + elif stop_after < 0: + raise ValueError( + f"enumpoly_solve(): " + f"stop_after argument must be a non-negative number; got {stop_after}" + ) + if maxregret <= 0.0: + raise ValueError( + f"enumpoly_solve(): maxregret must be a positive number; got {maxregret}" + ) + if phcpack_path is not None: + if game.is_tree and not use_strategic: + raise ValueError( + "enumpoly_solve(): only solving on the strategic representation is " + "supported by the PHCpack implementation" + ) + equilibria = nashphc.phcpack_solve(game, phcpack_path, maxregret) + return NashComputationResult( + game=game, + method="enumpoly", + rational=False, + use_strategic=False, + parameters={"stop_after": stop_after, "maxregret": maxregret, + "phcpack_path": phcpack_path}, + equilibria=equilibria, + ) + + if not game.is_tree or use_strategic: + equilibria = libgbt._enumpoly_strategy_solve(game, stop_after, maxregret) + else: + equilibria = libgbt._enumpoly_behavior_solve(game, stop_after, maxregret) + return NashComputationResult( + game=game, + method="enumpoly", + rational=False, + use_strategic=not game.is_tree or use_strategic, + parameters={"stop_after": stop_after, "maxregret": maxregret}, + equilibria=equilibria, + ) + + def logit_solve( game: libgbt.Game, use_strategic: bool = False, @@ -573,6 +709,10 @@ def logit_solve( """ if maxregret <= 0.0: raise ValueError("logit_solve(): maxregret argument must be positive") + if first_step <= 0.0: + raise ValueError("logit_solve(): first_step argument must be positive") + if max_accel < 1.0: + raise ValueError("logit_solve(): max_accel argument must be at least 1.0") if not game.is_tree or use_strategic: equilibria = libgbt._logit_strategy_solve(game, maxregret, first_step, max_accel) else: @@ -585,7 +725,3 @@ def logit_solve( equilibria=equilibria, parameters={"first_step": first_step, "max_accel": max_accel}, ) - - -logit_atlambda = libgbt.logit_atlambda -logit_principal_branch = libgbt.logit_principal_branch diff --git a/src/pygambit/nashlrs.py b/src/pygambit/nashlrs.py new file mode 100644 index 000000000..eb80c15e7 --- /dev/null +++ b/src/pygambit/nashlrs.py @@ -0,0 +1,69 @@ +"""Interface to external standalone tool lrsnash. +""" + +from __future__ import annotations + +import itertools +import pathlib +import subprocess +import sys + +import pygambit as gbt +import pygambit.util as util + + +def _generate_lrs_input(game: gbt.Game) -> str: + s = f"{len(game.players[0].strategies)} {len(game.players[1].strategies)}\n\n" + for st1 in game.players[0].strategies: + s += " ".join(str(gbt.Rational(game[st1, st2][game.players[0]])) + for st2 in game.players[1].strategies) + "\n" + s += "\n" + for st1 in game.players[0].strategies: + s += " ".join(str(gbt.Rational(game[st1, st2][game.players[1]])) + for st2 in game.players[1].strategies) + "\n" + return s + + +def _parse_lrs_output(game: gbt.Game, txt: str) -> list[gbt.MixedStrategyProfileRational]: + data = "\n".join([x for x in txt.splitlines() if not x.startswith("*")]).strip() + eqa = [] + for component in data.split("\n\n"): + profiles = {"1": [], "2": []} + for line in component.strip().splitlines(): + profiles[line[0]].append([gbt.Rational(x) for x in line[1:].strip().split()[:-1]]) + for p1, p2 in itertools.product(profiles["1"], profiles["2"]): + eqa.append(game.mixed_strategy_profile([p1, p2], rational=True)) + return eqa + + +def lrsnash_solve(game: gbt.Game, + lrsnash_path: pathlib.Path | str) -> list[gbt.MixedStrategyProfileRational]: + if len(game.players) != 2: + raise RuntimeError("Method only valid for two-player games.") + with util.make_temporary(_generate_lrs_input(game)) as infn: + result = subprocess.run([lrsnash_path, infn], encoding="utf-8", + stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + if result.returncode != 0: + raise ValueError(f"PHC run failed with return code {result.returncode}") + return _parse_lrs_output(game, result.stdout) + + +def _read_game(fn: str) -> gbt.Game: + for reader in [gbt.read_efg, gbt.read_nfg, gbt.read_agg]: + try: + return reader(fn) + except Exception: + pass + raise OSError(f"Unable to read or parse {fn}") + + +def main(): + game = _read_game(sys.argv[1]) + eqa = lrsnash_solve(game, "./lrsnash") + for eqm in eqa: + print("NE," + + ",".join(str(eqm[strat]) for player in game.players for strat in player.strategies)) + + +if __name__ == "__main__": + main() diff --git a/src/pygambit/nashphc.py b/src/pygambit/nashphc.py new file mode 100644 index 000000000..d8737e7e4 --- /dev/null +++ b/src/pygambit/nashphc.py @@ -0,0 +1,265 @@ +"""Enumerate Nash equilibria by solving systems of polynomial equations using PHCpack. +""" + +from __future__ import annotations + +import contextlib +import itertools +import pathlib +import string +import subprocess +import sys +import typing + +import pygambit as gbt +import pygambit.util as util + + +def _process_phc_output(output: str) -> list[dict]: + """Parse the output file from a run of PHC pack and produce a list of dictionaries, + with each element in the list corresponding to one of the solutions found. + """ + startsol = output.find("THE SOLUTIONS :\n\n") + if startsol == -1: + startsol = output.find("THE SOLUTIONS :\n") + + solns = output[startsol:] + firstequals = solns.find("solution") + firstcut = solns[firstequals:] + + secondequals = firstcut.find("=====") + if secondequals >= 0: + secondcut = firstcut[:secondequals] + else: + secondequals = firstcut.find("TIMING") + secondcut = firstcut[:secondequals] + + solutions = [] + for line in secondcut.split("\n"): + tokens = [x.strip() for x in line.split() if x and not x.isspace()] + + if not tokens: + continue + + if tokens[0] == "solution": + if len(tokens) == 3: + # This is a solution that didn't involve iteration + solutions.append({"vars": {}}) + else: + solutions.append({ + "startresidual": float(tokens[6]), + "iterations": int(tokens[9]), + "result": tokens[10], + "vars": {} + }) + elif tokens[0] == "t": + solutions[-1]["t"] = complex(float(tokens[2]), + float(tokens[3])) + elif tokens[0] == "m": + solutions[-1]["m"] = int(tokens[2]) + elif tokens[0] == "the": + pass + elif tokens[0] == "==": + solutions[-1]["err"] = float(tokens[3]) + solutions[-1]["rco"] = float(tokens[7]) + solutions[-1]["res"] = float(tokens[11]) + with contextlib.suppress(IndexError): + # Some solutions don't have type information + solutions[-1]["type"] = " ".join([tokens[13], tokens[14]]) + else: + # This is a solution line + solutions[-1]["vars"][tokens[0]] = complex(float(tokens[2]), + float(tokens[3])) + return solutions + + +def _run_phc(phcpack_path: pathlib.Path | str, equations: list[str]) -> list[dict]: + """Call PHCpack via an external binary to solve a set of equations, and return + the details on solutions found. + + Parameters + ---------- + phcpack_path : pathlib.Path or str + The path to the PHC program binary + + equations : list[str] + The set of equations to solve, expressed as string representations of the + polynomials. + + Returns + ------- + A list of dictionaries, each representing one solution. Each dictionary + may contain the following keys: + - `vars': A dictionary whose keys are the variable names, + and whose values are the solution values, represented + using the Python `complex` type. + - `type': The text string PHCpack emits describing the output + (e.g., "no solution", "real regular", etc.) + - `startresidual' + - `iterations' + - `result' + - `t' + - `m' + - `err' + - `rco' + - `res' + """ + with ( + util.make_temporary(f"{len(equations)}\n" + + ";\n".join(equations) + ";\n\n\n") as infn, + util.make_temporary() as outfn + ): + result = subprocess.run([phcpack_path, "-b", infn, outfn]) + if result.returncode != 0: + raise ValueError(f"PHC run failed with return code {result.returncode}") + with outfn.open() as outfile: + return _process_phc_output(outfile.read()) + + +# Use this table to assign letters to player strategy variables +# Skip 'e', 'i', and 'j', because PHC doesn't allow these in variable names. +_playerletters = [c for c in string.ascii_lowercase if c != ["e", "i", "j"]] + + +def _contingencies( + support: gbt.StrategySupportProfile, + skip_player: gbt.Player +) -> typing.Generator[list[gbt.Strategy], None, None]: + """Generate all contingencies of strategies in `support` for all players + except player `skip_player`. + """ + for profile in itertools.product( + *[[strategy for strategy in player.strategies if strategy in support] + if player != skip_player else [None] + for player in support.game.players] + ): + yield list(profile) + + +def _equilibrium_equations(support: gbt.StrategySupportProfile, player: gbt.Player) -> list: + """Generate the equations that the strategy of `player` must satisfy in any + totally-mixed equilibrium on `support`. + """ + payoffs = {strategy: [] for strategy in player.strategies if strategy in support} + + strategies = list(support[player]) + for profile in _contingencies(support, player): + contingency = "*".join(f"{_playerletters[strat.player.number]}{strat.number}" + for strat in profile if strat is not None) + for strategy in strategies: + profile[player.number] = strategy + if support.game[profile][player] != 0: + payoffs[strategy].append(f"({support.game[profile][player]}*{contingency})") + + payoffs = {s: "+".join(v) for s, v in payoffs.items()} + equations = [f"({payoffs[strategies[0]]})-({payoffs[s]})" for s in strategies[1:]] + equations.append("+".join(_playerletters[player.number] + str(strat.number) + for strat in strategies) + "-1") + return equations + + +def _is_nash(profile: gbt.MixedStrategyProfile, maxregret: float, negtol: float) -> bool: + """Check if the profile is an (approximate) Nash equilibrium, allowing a maximum + regret of `maxregret` and a tolerance of (small) negative probabilities of `negtol`.""" + for player in profile.game.players: + for strategy in player.strategies: + if profile[strategy] < -negtol: + return False + return profile.max_regret() < maxregret + + +def _solution_to_profile(game: gbt.Game, entry: dict) -> gbt.MixedStrategyProfileDouble: + profile = game.mixed_strategy_profile() + for player in game.players: + playerchar = _playerletters[player.number] + for strategy in player.strategies: + try: + profile[strategy] = entry["vars"][playerchar + str(strategy.number)].real + except KeyError: + profile[strategy] = 0.0 + return profile + + +def _format_support(support, label: str) -> str: + strings = ["".join(str(int(strategy in support)) for strategy in player.strategies) + for player in support.game.players] + return label + "," + ",".join(strings) + + +def _format_profile(profile: gbt.MixedStrategyProfileDouble, label: str, + decimals: int = 6) -> str: + """Render the mixed strategy profile `profile` to a one-line string with the given + `label`. + """ + return (f"{label}," + + ",".join(["{p:.{decimals}f}".format(p=profile[s], decimals=decimals) + for player in profile.game.players for s in player.strategies])) + + +def _profile_from_support(support: gbt.StrategySupportProfile) -> gbt.MixedStrategyProfileDouble: + """Construct a mixed strategy profile corresponding to the (pure strategy) equilibrium + on `support`. + """ + profile = support.game.mixed_strategy_profile() + for player in support.game.players: + for strategy in player.strategies: + profile[strategy] = 1.0 if strategy in support else 0.0 + return profile + + +def _solve_support(support: gbt.StrategySupportProfile, + phcpack_path: pathlib.Path | str, + maxregret: float, + negtol: float, + onsupport=lambda x, label: None, + onequilibrium=lambda x, label: None) -> list[gbt.MixedStrategyProfileDouble]: + onsupport(support, "candidate") + if len(support) == len(support.game.players): + profiles = [_profile_from_support(support)] + else: + eqns = [eqn + for player in support.game.players + for eqn in _equilibrium_equations(support, player)] + try: + profiles = [ + _solution_to_profile(support.game, entry) + for entry in _run_phc(phcpack_path, eqns) + ] + except ValueError: + onsupport(support, "singular") + profiles = [] + except Exception: + onsupport(support, "singular") + raise + profiles = [p for p in profiles if _is_nash(p, maxregret, negtol)] + for profile in profiles: + onequilibrium(profile, "NE") + return profiles + + +def phcpack_solve(game: gbt.Game, phcpack_path: pathlib.Path | str, + maxregret: float) -> list[gbt.MixedStrategyProfileDouble]: + negtol = 1.0e-6 + return [ + eqm + for support in gbt.nash.possible_nash_supports(game) + for eqm in _solve_support(support, phcpack_path, maxregret, negtol) + ] + + +def _read_game(fn: str) -> gbt.Game: + for reader in [gbt.read_efg, gbt.read_nfg, gbt.read_agg]: + try: + return reader(fn) + except Exception: + pass + raise OSError(f"Unable to read or parse {fn}") + + +def main(): + game = _read_game(sys.argv[1]) + phcpack_solve(game, "./phc", maxregret=1.0e-6) + + +if __name__ == "__main__": + main() 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/pctrace.py b/src/pygambit/pctrace.py deleted file mode 100644 index 9fa71261e..000000000 --- a/src/pygambit/pctrace.py +++ /dev/null @@ -1,469 +0,0 @@ -# -# This file is part of Gambit -# Copyright (c) 1994-2025, The Gambit Project (https://www.gambit-project.org) -# -# FILE: src/python/gambit/pctrace.py -# Trace a smooth parameterized curve using a predictor-corrector method -# -# 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. -# -"""Trace a smooth parameterized curve using a predictor-corrector method. -""" -import math - -import numpy -import scipy.linalg - - -def givens(b, q, c1, c2, ell1, ell2, ell3): - if math.fabs(c1) + math.fabs(c2) == 0.0: - return c1, c2 - - if math.fabs(c2) >= math.fabs(c1): - sn = math.sqrt(1.0 + (c1/c2)*(c1/c2)) * math.fabs(c2) - else: - sn = math.sqrt(1.0 + (c2/c1)*(c2/c1)) * math.fabs(c1) - s1 = c1/sn - s2 = c2/sn - - for k in range(q.shape[1]): - sv1 = q[ell1, k] - sv2 = q[ell2, k] - q[ell1, k] = s1*sv1 + s2*sv2 - q[ell2, k] = -s2*sv1 + s1*sv2 - - for k in range(ell3, b.shape[1]): - sv1 = b[ell1, k] - sv2 = b[ell2, k] - b[ell1, k] = s1*sv1 + s2*sv2 - b[ell2, k] = -s2*sv1 + s1*sv2 - - return sn, 0.0 - - -def qr_decomp_orig(b): - # An implementation of QR decomposition based on Gambit's C++ - # path-following module (based in tern on Allgower and Georg) - q = numpy.identity(b.shape[0]) - for m in range(b.shape[1]): - for k in range(m+1, b.shape[0]): - b[m, m], b[k, m] = givens(b, q, b[m, m], b[k, m], m, k, m+1) - return q - - -def qr_decomp_scipy(b): - # Drop-in replacement for QR decomposition using SciPy's implementation. - # Unlike the approach in Allgower and Georg, SciPy's implementation - # does not result in a consistent sign convention for the R matrix - # and therefore does not work with bifurcation detection. - qq, b[:, :] = scipy.linalg.qr(b, overwrite_a=True) - return qq.transpose() - - -def newton_step(q, b, u, y): - # Expects q and b to be 2-D arrays, and u and y to be 1-D arrays - # Returns steplength - for k in range(b.shape[1]): - y[k] -= numpy.dot(b[:k, k], y[:k]) - y[k] /= b[k, k] - - d = 0.0 - for k in range(b.shape[0]): - s = numpy.dot(q[:-1, k], y) - u[k] -= s - d += s*s - return math.sqrt(d) - - -def trace_path(start, startLam, maxLam, compute_lhs, compute_jac, - omega=1.0, hStart=0.03, maxDecel=1.1, maxIter=1000, - crit=None, callback=None): - """ - Trace a differentiable path starting at the vector 'start' with - parameter 'startLam', until 'maxLam' is reached. lhs() returns the - value of the LHS at any point (x, lam), and jac() returns the value - of the Jacobian at any point (x, lam). omega determines the orientation - to trace the curve. Optionally, 'crit' is a function to search for a - zero of along the path. - """ - - tol = 1.0e-4 # tolerance for corrector iteration - maxDist = 0.4 # maximal distance to curve - maxContr = 0.6 # maximal contraction rate in corrector - # perturbation to avoid cancellation in calculating contraction rate - eta = 0.1 - h = hStart # initial stepsize - hmin = 1.0e-8 # minimal stepsize - - newton = False # using Newton steplength (for zero-finding) - - x = numpy.array([x for x in start] + [startLam]) - if callback is not None: - callback(x) - - y = numpy.zeros(len(start)) - q = numpy.zeros((len(start) + 1, len(start) + 1)) - b = compute_jac(x) - q = qr_decomp_scipy(b) - t = q[-1] # last column is tangent - - if startLam == 0.0 and omega*t[-1] < 0.0: - # Reverse orientation of curve to trace away from boundary - omega = -omega - - while x[-1] >= 0.0 and x[-1] < maxLam: - accept = True - - if abs(h) <= hmin: - # Stop. - return x - - # Predictor step - u = x + h*omega*t - - decel = 1.0 / maxDecel # initialize deceleration factor - b = compute_jac(u) - # q, b = scipy.linalg.decomp.qr(b, mode='qr') - q = qr_decomp_scipy(b) - - it = 1 - disto = 0.0 - while True: - if it == maxIter: - # Give up! - return x - - y = compute_lhs(u) - dist = newton_step(q, b, u, y) - - if dist >= maxDist: - accept = False - break - - decel = max(decel, math.sqrt(dist / maxDist) * maxDecel) - - if it >= 2: - contr = dist / (disto + tol * eta) - if contr > maxContr: - accept = False - break - decel = max(decel, math.sqrt(contr / maxContr) * maxDecel) - - if dist < tol: - # Success; break out of iteration - break - - disto = dist - it += 1 - - if not accept: - h /= maxDecel # PC not accepted; change stepsize and retry - if abs(h) < hmin: - # Stop. - return x - continue # back out to main loop to try again - - # Determine new stepsize - if decel > maxDecel: - decel = maxDecel - - if not newton and crit is not None: - # Currently, 't' is the tangent at 'x'. We also need the - # tangent at 'u'. - newT = q[-1] - - if crit(x, t) * crit(u, newT) < 0.0: - # Enter Newton mode, since a critical point has been bracketed - newton = True - - if newton: - # Newton-type steplength adaptation, secant method - newT = q[-1] - h *= -crit(u, newT) / (crit(u, newT) - crit(x, t)) - else: - # Standard steplength adaptaiton - h = abs(h / decel) - - # PC step was successful; update and iterate - x = u[:] - - if callback is not None: - callback(x) - - newT = q[-1] # new tangent - if sum(t * newT) < 0.0: - # Bifurcation detected - # The Givens-rotation based version of the QR decomposition - # ensures the sense of the tangent is consistent. - # The SciPy implementation does not guarantee anything about - # the chosen sign of the decomposition. This results in - # spurious detections of bifurcations (as well as missing - # bifurcations that do exist). - # The SciPy implementation is MUCH faster, so for the - # moment we use that implementation and suppress bifurcation - # detection. - # print(f"Detected bifurcation near {x[-1]:f}") - omega = -omega - t = newT[:] - - return x - - -def upd(q, b, x, u, y, w, t, h, angmax): - n = len(w) - n1 = n + 1 - - for k in range(n): - b[n1-1, k] = (w[k] - y[k]) / h - - for k in range(n): - givens(b, q, b[k, k], b[n1-1, k], k, n1-1, k) - - ang = 0.0 - for k in range(n1): - ang = ang + t[k] * q[n1-1, k] - - if ang > 1.0: - ang = 1.0 - if ang < -1.0: - ang = -1.0 - - return math.acos(ang) <= angmax - - -def ynorm(y): - s = 0.0 - for i in range(len(y)): - s += y[i]**2 - return math.sqrt(s) - - -def newt(q, b, u, v, w, p, pv, r, pert, dmax, dmin, ctmax, cdmax, - compute_lhs): - # input: q, b, u, w=H(u) - # output: v, r=H(v) - # w is changed - # one Newton step is performed - # q, b = QR decomposition of A - # q, b are updated - # perturbations are used for stabilization - # residual and contraction tests are performed - - test = True - n = len(w) - n1 = n+1 - - for k in range(n): - if abs(w[k]) > pert: - pv[k] = 0.0 - elif w[k] > 0.0: - pv[k] = w[k] - pert - else: - pv[k] = w[k] + pert - w[k] = w[k] - pv[k] - - d1 = ynorm(w) - - if d1 > dmax: - print("newt: fail on LHS norm test") - return False, r, v - - for k in range(n): - for ell in range(k-1): - w[k] = w[k] - b[ell, k] * w[ell] - w[k] = w[k] / b[k, k] - - d2 = ynorm(w) - - for k in range(n1): - s = 0.0 - for ell in range(n): - s = s + q[ell, k] * w[ell] - v[k] = u[k] - s - - r = compute_lhs(v) - - for k in range(n): - p[k] = r[k] - pv[k] - - d3 = ynorm(p) - contr = d3 / (d1 + dmin) - if contr > ctmax: - print("newt: fail on contraction test") - test = False - - for k in reversed(range(n-1)): - givens(b, q, w[k], w[k+1], k, k+1, k) - - for k in range(n): - b[0, k] = b[0, k] - p[k] / d2 - - for k in range(n-1): - givens(b, q, b[k, k], b[k+1, k], k, k+1, k) - - if b[n-1, n-1] < 0.0: - print("newt: fail on diagonal sign test") - test = False - b[n-1, n-1] = -b[n-1, n-1] - for k in range(n1): - q[n-1, k] = -q[n-1, k] - q[n1-1, k] = -q[n1-1, k] - - for i in range(1, n): - for k in range(i-1): - if abs(b[k, i]) > cdmax * abs(b[i, i]): - print("... reconditioning ...") - if b[i, i] > 0.0: - b[i, i] = abs(b[k, i]) / cdmax - else: - b[i, i] = -abs(b[k, i]) / cdmax - - for k in range(n-1): - b[k+1, k] = 0.0 - - return test, r, v - - -def estimate_jac(x, compute_lhs): - n = len(x)-1 - n1 = len(x) - b = numpy.zeros((n1, n)) - h = 0.32 - - for i in range(n1): - x[i] = x[i] + h - y = compute_lhs(x) - x[i] = x[i] - h - for k in range(n): - b[i, k] = y[k] - - y = compute_lhs(x) - - for i in range(n1): - for k in range(n): - b[i, k] = (b[i, k] - y[k]) / h - - return b - - -def trace_path_nojac(start, startLam, maxLam, compute_lhs, - omega=1.0, hStart=0.03, maxDecel=1.1, maxIter=1000, - crit=None, callback=None): - """ - Trace a differentiable path starting at the vector 'start' with - parameter 'startLam', until 'maxLam' is reached. lhs() returns the - value of the LHS at any point (x, lam), and jac() returns the value - of the Jacobian at any point (x, lam). omega determines the orientation - to trace the curve. Optionally, 'crit' is a function to search for a - zero of along the path. - """ - - # tol = 1.0e-4 # tolerance for corrector iteration - # maxDist = 0.4 # maximal distance to curve - # maxContr = 0.6 # maximal contraction rate in corrector - # perturbation to avoid cancellation in calculating contraction rate - # eta = 0.1 - h = hStart # initial stepsize - hmin = 1.0e-8 # minimal stepsize - - ctmax = 0.8 - dmax = 0.01 - dmin = 0.0001 - pert = 0.00001 - # hmax = 1.28 - hmin = 0.000001 - h = 0.03 - cdmax = 1000.0 - angmax = 3.141592654 / 3 - # maxstp = 9000 - acfac = 1.1 - - newton = False # using Newton steplength (for zero-finding) - - x = numpy.array([x for x in start] + [startLam]) - if callback is not None: - callback(x) - - y = numpy.zeros(len(start)) - q = numpy.zeros((len(start)+1, len(start)+1)) - v = numpy.zeros(len(start)+1) - p = numpy.zeros(len(start)) - pv = numpy.zeros(len(start)) - r = numpy.zeros(len(start)) - - b = estimate_jac(x, compute_lhs) - q = qr_decomp_orig(b) - - while x[-1] >= 0.0 and x[-1] < maxLam: - qq = q[:, :] - bb = b[:, :] - - t = q[-1][:] # last column is tangent - - if abs(h) <= hmin: - # Stop. - return x - - # Predictor step - u = x + h*omega*t - print("PREDICTOR") - callback(u) - w = compute_lhs(u) - print(w) - print(ynorm(w)) - - test = upd(q, b, x, u, y, w, t, h, angmax) - if not test: - print("Fail angle test...") - h = h / acfac - q = qq - b = bb - continue - - test, r, v = newt( - q, b, u, v, w, p, pv, r, pert, dmax, dmin, ctmax, cdmax, - compute_lhs - ) - if not test: - print("Fail Newton test...") - h = h / acfac - q = qq - b = bb - continue - - if newton: - # Newton-type steplength adaptation - h = -(v[-1] - 1.0) / q[-1, -1] - if abs(h) <= hmin: - # Stop - return x - else: - # Standard steplength adaptaiton - h = abs(h) * acfac - # if h > hmax: - # h = hmax - - # PC step was successful; update and iterate - x = v[:] - y = r[:] - - if callback is not None: - print("ACCEPTED") - callback(x) - print(y) - print(ynorm(y)) - print() - - return x 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/qre.py b/src/pygambit/qre.py index 0a95946ec..01162f27a 100644 --- a/src/pygambit/qre.py +++ b/src/pygambit/qre.py @@ -22,285 +22,105 @@ """ A set of utilities for computing and analyzing quantal response equilbria """ -import contextlib +from __future__ import annotations + import math -import numpy import scipy.optimize -from . import gambit, pctrace -from .profiles import Solution - - -def sym_compute_lhs(game, point): - """ - Compute the LHS for the set of equations for a symmetric logit QRE - of a symmetric game. - """ - profile = game.mixed_strategy_profile( - point=[math.exp(x) for x in point[:-1]] - ) - logprofile = point[:-1] - lam = point[-1] - - lhs = numpy.zeros(len(profile)) - - for st in range(len(game.choices)): - if st == 0: - # sum-to-one equation - lhs[st] = -1.0 + sum(profile) - else: - lhs[st] = (logprofile[st] - logprofile[0] - - lam * (profile.strategy_value(st) - - profile.strategy_value(0))) - return lhs - - -def sym_compute_jac(game, point): - """ - Compute the Jacobian for the set of equations for a symmetric logit QRE - of a symmetric game. - """ - profile = game.mixed_strategy_profile( - point=[math.exp(x) for x in point[:-1]] - ) - lam = point[-1] - - matrix = numpy.zeros((len(point), len(profile))) +import pygambit.gambit as libgbt + + +def logit_solve_branch( + game: libgbt.Game, + use_strategic: bool = False, + maxregret: float = 1.0e-8, + first_step: float = .03, + max_accel: float = 1.1, +): + if maxregret <= 0.0: + raise ValueError("logit_solve_branch(): maxregret argument must be positive") + if first_step <= 0.0: + raise ValueError("logit_solve_branch(): first_step argument must be positive") + if max_accel < 1.0: + raise ValueError("logit_solve_branch(): max_accel argument must be at least 1.0") + if not game.is_tree or use_strategic: + return libgbt._logit_strategy_branch(game, maxregret, first_step, max_accel) + else: + return libgbt._logit_behavior_branch(game, maxregret, first_step, max_accel) + + +def logit_solve_lambda( + game: libgbt.Game, + lam: float | list[float], + use_strategic: bool = False, + first_step: float = .03, + max_accel: float = 1.1, +): + if first_step <= 0.0: + raise ValueError("logit_solve_lambda(): first_step argument must be positive") + if max_accel < 1.0: + raise ValueError("logit_solve_lambda(): max_accel argument must be at least 1.0") + if not game.is_tree or use_strategic: + return libgbt._logit_strategy_lambda(game, lam, first_step, max_accel) + else: + return libgbt._logit_behavior_lambda(game, lam, first_step, max_accel) - for st in range(len(game.choices)): - if st == 0: - # sum-to-one equation - for sto in range(len(game.choices)): - matrix[sto, st] = profile[sto] - - # derivative wrt lambda is zero, so don't need to fill last col - else: - # this is a ratio equation - for sto in range(len(game.choices)): - matrix[sto, st] = ( - -(game.N-1) * lam * profile[sto] * - (profile.strategy_value_deriv(st, sto) - - profile.strategy_value_deriv(0, sto)) - ) - if sto == 0: - matrix[sto, st] -= 1.0 - elif sto == st: - matrix[sto, st] += 1.0 - - # column wrt lambda - matrix[-1][st] = ( - profile.strategy_value(0) - profile.strategy_value(st) - ) - - return matrix - - -def printer(game, point): - profile = game.mixed_strategy_profile( - point=[math.exp(x) for x in point[:-1]] - ) - lam = point[-1] - print(lam, profile) +class LogitQREMixedStrategyFitResult: + """The result of fitting a QRE to a given probability distribution + over strategies. -class LogitQRE(Solution): - """ - Container class representing a logit QRE + See Also + -------- + logit_estimate """ - def __init__(self, lam, profile): - Solution.__init__(self, profile) + def __init__(self, data, method, lam, profile, log_like): + self._data = data + self._method = method self._lam = lam + self._profile = profile + self._log_like = log_like - def __repr__(self): - return f"" + @property + def method(self) -> str: + """The method used to estimate the QRE; either "fixedpoint" or "empirical".""" + return self._method + + @property + def data(self) -> libgbt.MixedStrategyProfileDouble: + """The empirical strategy frequencies used to estimate the QRE.""" + return self._data @property - def lam(self): + def lam(self) -> float: + """The value of lambda corresponding to the QRE.""" return self._lam @property - def mu(self): - return 1.0 / self._lam + def profile(self) -> libgbt.MixedStrategyProfileDouble: + """The mixed strategy profile corresponding to the QRE.""" + return self._profile + @property + def log_like(self) -> float: + """The log-likelihood of the data at the estimated QRE.""" + return self._log_like -class StrategicQREPathTracer: - """ - Compute the principal branch of the logit QRE correspondence of 'game'. - """ - def __init__(self): - self.h_start = 0.03 - self.max_decel = 1.1 - - def trace_strategic_path(self, game, max_lambda=1000000.0, callback=None): - def on_step(game, points, p, callback): - qre = LogitQRE( - p[-1], - game.mixed_strategy_profile( - point=[math.exp(x) for x in p[:-1]] - ) - ) - points.append(qre) - if callback: - callback(qre) - - points = [] - if game.is_symmetric: - p = game.mixed_strategy_profile() - - with contextlib.suppress(KeyboardInterrupt): - pctrace.trace_path( - [math.log(x) for x in p.profile], - 0.0, max_lambda, - lambda x: sym_compute_lhs(game, x), - lambda x: sym_compute_jac(game, x), - hStart=self.h_start, - maxDecel=self.max_decel, - callback=lambda p: on_step(game, points, p, callback), - crit=None, - maxIter=100 - ) - - return points - else: - raise NotImplementedError - - def compute_at_lambda(self, game, lam, callback=None): - def on_step(p): - return callback( - LogitQRE(p[-1], - game.mixed_strategy_profile( - point=[math.exp(x) for x in p[:-1]] - )) - ) - - if game.is_symmetric: - p = game.mixed_strategy_profile() - - point = pctrace.trace_path( - [math.log(x) for x in p.profile], - 0.0, 1000000.0, - lambda x: sym_compute_lhs(game, x), - lambda x: sym_compute_jac(game, x), - crit=lambda x, t: x[-1] - lam, - callback=on_step if callback is not None else None, - maxIter=100 - ) - - return LogitQRE( - point[-1], - game.mixed_strategy_profile( - point=[math.exp(x) for x in point[:-1]]) - ) - else: - raise NotImplementedError - - def compute_max_like(self, game, data): - def log_like(data, profile): - return sum(x*math.log(y) for (x, y) in zip(data, profile)) - - def diff_log_like(data, point, tangent): - return sum(x*y for (x, y) in zip(data, tangent[:-1])) - - if game.is_symmetric: - p = game.mixed_strategy_profile() - - point = pctrace.trace_path( - [math.log(x) for x in p.profile], - 0.0, 1000000.0, - lambda x: sym_compute_lhs(game, x), - lambda x: sym_compute_jac(game, x), - hStart=1.0, - crit=lambda x, t: diff_log_like(data, x, t), - maxIter=100 - ) - - qre = LogitQRE( - point[-1], - game.mixed_strategy_profile( - point=[math.exp(x) for x in point[:-1]] - ) - ) - qre.logL = log_like(data, qre) - return qre - else: - raise NotImplementedError - - def compute_fit_sshist(self, game, data, callback=None): - """ - Find lambda parameter for which QRE best fits the data - using the sum of squares of distances in the histogram of - the data. - """ - def diff_dist(data, point, tangent): - return 2.0 * sum((math.exp(p)-d) * t * math.exp(p) - for (p, t, d) in zip(point, tangent, data)) - - if game.is_symmetric: - p = game.mixed_strategy_profile() - point = pctrace.trace_path([math.log(x) for x in p.profile], - 0.0, 1000000.0, - lambda x: sym_compute_lhs(game, x), - lambda x: sym_compute_jac(game, x), - hStart=1.0, - crit=lambda x, t: diff_dist(data, x, t), - maxIter=100, - callback=callback) - - qre = LogitQRE( - point[-1], - game.mixed_strategy_profile( - point=[math.exp(x) for x in point[:-1]] - ) - ) - return qre - else: - raise NotImplementedError - - def compute_criterion(self, game, f): - def criterion_wrap(x, t): - """ - This translates the internal representation of the tracer - into a QRE object. - """ - return f( - LogitQRE( - x[-1], - game.mixed_strategy_profile( - point=[math.exp(z) for z in x[:-1]] - ) - ) - ) - - if game.is_symmetric: - p = game.mixed_strategy_profile() - - point = pctrace.trace_path([math.log(x) for x in p.profile], - 0.0, 1000000.0, - lambda x: sym_compute_lhs(game, x), - lambda x: sym_compute_jac(game, x), - hStart=1.0, - crit=criterion_wrap, - maxIter=100) - - return LogitQRE( - point[-1], - game.mixed_strategy_profile( - point=[math.exp(x) for x in point[:-1]] - ) - ) - else: - raise NotImplementedError + def __repr__(self) -> str: + return ( + f"" + ) -class LogitQREMixedStrategyFitResult: +class LogitQREMixedBehaviorFitResult: """The result of fitting a QRE to a given probability distribution - over strategies. + over actions. See Also -------- - fit_fixedpoint - fit_empirical + logit_estimate """ def __init__(self, data, method, lam, profile, log_like): self._data = data @@ -315,8 +135,8 @@ def method(self) -> str: return self._method @property - def data(self) -> gambit.MixedStrategyProfileDouble: - """The empirical strategy frequencies used to estimate the QRE.""" + def data(self) -> libgbt.MixedBehaviorProfileDouble: + """The empirical actions frequencies used to estimate the QRE.""" return self._data @property @@ -325,8 +145,8 @@ def lam(self) -> float: return self._lam @property - def profile(self) -> gambit.MixedStrategyProfileDouble: - """The mixed strategy profile corresponding to the QRE.""" + def profile(self) -> libgbt.MixedBehaviorProfileDouble: + """The mixed behavior profile corresponding to the QRE.""" return self._profile @property @@ -334,73 +154,158 @@ def log_like(self) -> float: """The log-likelihood of the data at the estimated QRE.""" return self._log_like - def __repr__(self): + def __repr__(self) -> str: return ( - f"" ) -def fit_fixedpoint( - data: gambit.MixedStrategyProfileDouble +def _estimate_strategy_fixedpoint( + data: libgbt.MixedStrategyProfileDouble, + local_max: bool = False, + first_step: float = .03, + max_accel: float = 1.1, ) -> LogitQREMixedStrategyFitResult: + res = libgbt._logit_strategy_estimate(data, local_max=local_max, + first_step=first_step, max_accel=max_accel) + return LogitQREMixedStrategyFitResult( + data, "fixedpoint", res.lam, res.profile, res.log_like + ) + + +def _estimate_behavior_fixedpoint( + data: libgbt.MixedBehaviorProfileDouble, + local_max: bool = False, + first_step: float = .03, + max_accel: float = 1.1, +) -> LogitQREMixedBehaviorFitResult: + res = libgbt._logit_behavior_estimate(data, local_max=local_max, + first_step=first_step, max_accel=max_accel) + return LogitQREMixedBehaviorFitResult( + data, "fixedpoint", res.lam, res.profile, res.log_like + ) + + +def _empirical_log_logit_probs(lam: float, regrets: list) -> list: + """Given empirical choice regrets and a value of lambda (`lam`), compute the + log-probabilities given by the logit choice model. + """ + log_sums = [ + math.log(sum([math.exp(lam*r) for r in infoset])) + for infoset in regrets + ] + return [lam*a - s for (r, s) in zip(regrets, log_sums) for a in r] + + +def _empirical_log_like(lam: float, regrets: list, flattened_data: list) -> float: + """Given empirical choice regrets and a list of frequencies of choices, compute + the log-likelihood of the choices given the regrets and assuming the logit + choice model with lambda `lam`.""" + return sum([f*p for (f, p) in zip(flattened_data, _empirical_log_logit_probs(lam, regrets))]) + + +def _estimate_strategy_empirical( + data: libgbt.MixedStrategyProfileDouble +) -> LogitQREMixedStrategyFitResult: + flattened_data = [data[s] for p in data.game.players for s in p.strategies] + normalized = data.normalize() + regrets = [[-normalized.strategy_regret(s) for s in player.strategies] + for player in data.game.players] + res = scipy.optimize.minimize( + lambda x: -_empirical_log_like(x[0], regrets, flattened_data), + (0.1,), + bounds=((0.0, None),) + ) + profile = data.game.mixed_strategy_profile() + for strategy, log_prob in zip(data.game.strategies, + _empirical_log_logit_probs(res.x[0], regrets)): + profile[strategy] = math.exp(log_prob) + return LogitQREMixedStrategyFitResult( + data, "empirical", res.x[0], profile, -res.fun + ) + + +def _estimate_behavior_empirical( + data: libgbt.MixedBehaviorProfileDouble, +) -> LogitQREMixedBehaviorFitResult: + flattened_data = [data[a] for p in data.game.players for s in p.infosets for a in s.actions] + normalized = data.normalize() + regrets = [[-normalized.action_regret(a) for a in infoset.actions] + for player in data.game.players for infoset in player.infosets] + res = scipy.optimize.minimize( + lambda x: -_empirical_log_like(x[0], regrets, flattened_data), + (0.1,), + bounds=((0.0, None),) + ) + profile = data.game.mixed_behavior_profile() + for action, log_prob in zip(data.game.actions, _empirical_log_logit_probs(res.x[0], regrets)): + profile[action] = math.exp(log_prob) + return LogitQREMixedBehaviorFitResult( + data, "empirical", res.x[0], profile, -res.fun + ) + + +def logit_estimate( + data: libgbt.MixedStrategyProfile | libgbt.MixedBehaviorProfile, + use_empirical: bool = False, + local_max: bool = False, + first_step: float = .03, + max_accel: float = 1.1, +) -> LogitQREMixedStrategyFitResult | LogitQREMixedBehaviorFitResult: """Use maximum likelihood estimation to find the logit quantal - response equilibrium on the principal branch for a strategic game - which best fits empirical frequencies of play. [1]_ + response equilibrium which best fits empirical frequencies of play. - .. versionadded:: 16.1.0 + .. versionadded:: 16.3.0 Parameters ---------- - data : MixedStrategyProfileDouble + data : MixedStrategyProfile or MixedBehaviorProfile The empirical distribution of play to which to fit the QRE. To obtain the correct resulting log-likelihood, these should - be expressed as total counts of observations of each strategy - rather than probabilities. + be expressed as total counts of observations of each action + rather than probabilities. If a MixedBehaviorProfile is + specified, estimation is done using the agent QRE. - Returns - ------- - LogitQREMixedStrategyFitResult - The result of the estimation represented as a - ``LogitQREMixedStrategyFitResult`` object. + use_empirical : bool, default = False + If specified and True, use the empirical payoff approach for + estimation. This replaces the payoff matrix of the game with an + approximation as a collection of individual decision problems based + on the empirical expected payoffs to strategies or actions. + This is computationally much faster but in most cases produces + estimates which deviate systematically from those obtained by + computing the QRE correspondence of the game. See the discussion + in [1]_ for more details. - See Also - -------- - fit_empirical : Estimate QRE by approximation of the correspondence - using independent decision problems. + local_max : bool, default False + The default behavior is to find the global maximiser along + the principal branch. If this parameter is set to True, + tracing stops at the first interior local maximiser found. - References - ---------- - .. [1] Bland, J. R. and Turocy, T. L., 2023. Quantal response equilibrium - as a structural model for estimation: The missing manual. - SSRN working paper 4425515. - """ - res = gambit.logit_estimate(data) - return LogitQREMixedStrategyFitResult( - data, "fixedpoint", res.lam, res.profile, res.log_like - ) + .. note:: + This argument only has an effect when use_empirical is False. -def fit_empirical( - data: gambit.MixedStrategyProfileDouble -) -> LogitQREMixedStrategyFitResult: - """Use maximum likelihood estimation to estimate a quantal - response equilibrium using the empirical payoff method. - The empirical payoff method operates by ignoring the fixed-point - considerations of the QRE and approximates instead by a collection - of independent decision problems. [1]_ + first_step : float, default .03 + The arclength of the initial step. - .. versionadded:: 16.1.0 + .. note:: + + This argument only has an effect when use_empirical is False. + + max_accel : float, default 1.1 + The maximum rate at which to lengthen the arclength step size. + + .. note:: + + This argument only has an effect when use_empirical is False. Returns ------- - LogitQREMixedStrategyFitResult + LogitQREMixedStrategyFitResult or LogitQREMixedBehaviorFitResult The result of the estimation represented as a - ``LogitQREMixedStrategyFitResult`` object. - - See Also - -------- - fit_fixedpoint : Estimate QRE precisely by computing the correspondence + ``LogitQREMixedStrategyFitResult`` or ``LogitQREMixedBehaviorFitResult`` + object, as appropriate. References ---------- @@ -408,24 +313,17 @@ def fit_empirical( as a structural model for estimation: The missing manual. SSRN working paper 4425515. """ - def do_logit(lam: float): - logit_probs = [[math.exp(lam*v) for v in player] for player in values] - sums = [sum(v) for v in logit_probs] - logit_probs = [[v/s for v in vv] - for (vv, s) in zip(logit_probs, sums)] - logit_probs = [v for player in logit_probs for v in player] - return [max(v, 1.0e-293) for v in logit_probs] - - def log_like(lam: float) -> float: - logit_probs = do_logit(lam) - return sum([f*math.log(p) for (f, p) in zip(list(flattened_data), logit_probs)]) - - flattened_data = [data[s] for p in data.game.players for s in p.strategies] - normalized = data.normalize() - values = [[normalized.strategy_value(s) for s in p.strategies] - for p in data.game.players] - res = scipy.optimize.minimize(lambda x: -log_like(x[0]), (0.1,), - bounds=((0.0, None),)) - return LogitQREMixedStrategyFitResult( - data, "empirical", res.x[0], do_logit(res.x[0]), -res.fun - ) + if isinstance(data, libgbt.MixedStrategyProfile): + if use_empirical: + return _estimate_strategy_empirical(data) + else: + return _estimate_strategy_fixedpoint(data, local_max=local_max, + first_step=first_step, max_accel=max_accel) + elif isinstance(data, libgbt.MixedBehaviorProfile): + if use_empirical: + return _estimate_behavior_empirical(data) + else: + return _estimate_behavior_fixedpoint(data, local_max=local_max, + first_step=first_step, max_accel=max_accel) + else: + raise TypeError("data must be specified as a MixedStrategyProfile or MixedBehaviorProfile") diff --git a/src/pygambit/strategy.pxi b/src/pygambit/strategy.pxi index 2cef0aaaa..7e0611572 100644 --- a/src/pygambit/strategy.pxi +++ b/src/pygambit/strategy.pxi @@ -73,3 +73,46 @@ class Strategy: def number(self) -> int: """The number of the strategy.""" return self.strategy.deref().GetNumber() - 1 + + 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/pygambit/stratmixed.pxi b/src/pygambit/stratmixed.pxi index ee503dbfd..a5ee63707 100644 --- a/src/pygambit/stratmixed.pxi +++ b/src/pygambit/stratmixed.pxi @@ -562,11 +562,11 @@ class MixedStrategyProfile: @cython.cclass class MixedStrategyProfileDouble(MixedStrategyProfile): - profile = cython.declare(shared_ptr[c_MixedStrategyProfileDouble]) + profile = cython.declare(shared_ptr[c_MixedStrategyProfile[double]]) @staticmethod @cython.cfunc - def wrap(profile: shared_ptr[c_MixedStrategyProfileDouble]) -> MixedStrategyProfileDouble: + def wrap(profile: shared_ptr[c_MixedStrategyProfile[float]]) -> MixedStrategyProfileDouble: obj: MixedStrategyProfileDouble = ( MixedStrategyProfileDouble.__new__(MixedStrategyProfileDouble) ) @@ -617,17 +617,17 @@ class MixedStrategyProfileDouble(MixedStrategyProfile): def _copy(self) -> MixedStrategyProfileDouble: return MixedStrategyProfileDouble.wrap( - make_shared[c_MixedStrategyProfileDouble](deref(self.profile)) + make_shared[c_MixedStrategyProfile[double]](deref(self.profile)) ) def _as_behavior(self) -> MixedBehaviorProfileDouble: return MixedBehaviorProfileDouble.wrap( - make_shared[c_MixedBehaviorProfileDouble](deref(self.profile)) + make_shared[c_MixedBehaviorProfile[double]](deref(self.profile)) ) def _normalize(self) -> MixedStrategyProfileDouble: return MixedStrategyProfileDouble.wrap( - make_shared[c_MixedStrategyProfileDouble](deref(self.profile).Normalize()) + make_shared[c_MixedStrategyProfile[double]](deref(self.profile).Normalize()) ) @property @@ -637,11 +637,13 @@ class MixedStrategyProfileDouble(MixedStrategyProfile): @cython.cclass class MixedStrategyProfileRational(MixedStrategyProfile): - profile = cython.declare(shared_ptr[c_MixedStrategyProfileRational]) + profile = cython.declare(shared_ptr[c_MixedStrategyProfile[c_Rational]]) @staticmethod @cython.cfunc - def wrap(profile: shared_ptr[c_MixedStrategyProfileRational]) -> MixedStrategyProfileRational: + def wrap( + profile: shared_ptr[c_MixedStrategyProfile[c_Rational]] + ) -> MixedStrategyProfileRational: obj: MixedStrategyProfileRational = ( MixedStrategyProfileRational.__new__(MixedStrategyProfileRational) ) @@ -696,17 +698,17 @@ class MixedStrategyProfileRational(MixedStrategyProfile): def _copy(self) -> MixedStrategyProfileRational: return MixedStrategyProfileRational.wrap( - make_shared[c_MixedStrategyProfileRational](deref(self.profile)) + make_shared[c_MixedStrategyProfile[c_Rational]](deref(self.profile)) ) def _as_behavior(self) -> MixedBehaviorProfileRational: return MixedBehaviorProfileRational.wrap( - make_shared[c_MixedBehaviorProfileRational](deref(self.profile)) + make_shared[c_MixedBehaviorProfile[c_Rational]](deref(self.profile)) ) def _normalize(self) -> MixedStrategyProfileRational: return MixedStrategyProfileRational.wrap( - make_shared[c_MixedStrategyProfileRational](deref(self.profile).Normalize()) + make_shared[c_MixedStrategyProfile[c_Rational]](deref(self.profile).Normalize()) ) @property diff --git a/src/pygambit/stratspt.pxi b/src/pygambit/stratspt.pxi index b89a39bfa..8bec86957 100644 --- a/src/pygambit/stratspt.pxi +++ b/src/pygambit/stratspt.pxi @@ -3,7 +3,7 @@ # Copyright (c) 1994-2025, The Gambit Project (https://www.gambit-project.org) # # FILE: src/pygambit/stratspt.pxi -# Cython wrapper for strategy supports +# Cython wrapper for strategy support profiles # # 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 @@ -19,58 +19,66 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # +import io import cython from cython.operator cimport dereference as deref from libcpp.memory cimport unique_ptr +@cython.cclass +class StrategySupport: + """A set of strategies for a specified player in a `StrategySupportProfile`. + """ + _profile = cython.declare(StrategySupportProfile) + _player = cython.declare(Player) + + def __init__(self, profile: StrategySupportProfile, player: Player) -> None: + self._profile = profile + self._player = player + + @property + def player(self) -> Player: + return self._player + + def __iter__(self) -> typing.Generator[Strategy, None, None]: + for strat in deref(self._profile.profile).GetStrategies(self._player.player): + yield Strategy.wrap(strat) + + @cython.cclass class StrategySupportProfile: """A set-like object representing a subset of the strategies in game. A StrategySupportProfile always contains at least one strategy for each player in the game. """ - support = cython.declare(unique_ptr[c_StrategySupportProfile]) + profile = cython.declare(shared_ptr[c_StrategySupportProfile]) def __init__(self, *args, **kwargs) -> None: raise ValueError("Cannot create a StrategySupportProfile outside a Game.") @staticmethod @cython.cfunc - def wrap( - game: Game, - strategies: typing.Optional[typing.Iterable[Strategy]] = None - ) -> StrategySupportProfile: - if (strategies is not None and - len(set([strat.player.number for strat in strategies])) != len(game.players)): - raise ValueError( - "A StrategySupportProfile must have at least one strategy for each player" - ) + def wrap(profile: shared_ptr[c_StrategySupportProfile]) -> StrategySupportProfile: obj: StrategySupportProfile = StrategySupportProfile.__new__(StrategySupportProfile) - # There's at least one strategy for each player, so this forms a valid support profile - obj.support.reset(new c_StrategySupportProfile(game.game)) - if strategies is not None: - for strategy in game.strategies: - if strategy not in strategies: - deref(obj.support).RemoveStrategy(cython.cast(Strategy, strategy).strategy) + obj.profile = profile return obj @property def game(self) -> Game: """The `Game` on which the support profile is defined.""" - return Game.wrap(deref(self.support).GetGame()) + return Game.wrap(deref(self.profile).GetGame()) def __repr__(self) -> str: return f"StrategySupportProfile(game={self.game})" def __len__(self) -> int: """Returns the total number of strategies in the support profile.""" - return deref(self.support).MixedProfileLength() + return deref(self.profile).MixedProfileLength() def __eq__(self, other: typing.Any) -> bool: return ( isinstance(other, StrategySupportProfile) and - deref(self.support) == deref(cython.cast(StrategySupportProfile, other).support) + deref(self.profile) == deref(cython.cast(StrategySupportProfile, other).profile) ) def __le__(self, other: StrategySupportProfile) -> bool: @@ -79,32 +87,62 @@ class StrategySupportProfile: def __ge__(self, other: StrategySupportProfile) -> bool: return self.issuperset(other) - def __getitem__(self, index: int) -> Strategy: - for pl in range(len(self.game.players)): - if index < deref(self.support).NumStrategiesPlayer(pl+1): - return Strategy.wrap(deref(self.support).GetStrategy(pl+1, index+1)) - index = index - deref(self.support).NumStrategiesPlayer(pl+1) - raise IndexError("StrategySupportProfile index out of range") - def __contains__(self, strategy: Strategy) -> bool: if strategy not in self.game.strategies: raise MismatchError( "strategy is not part of the game on which the profile is defined." ) - return deref(self.support).Contains(strategy.strategy) + return deref(self.profile).Contains(strategy.strategy) def __iter__(self) -> typing.Generator[Strategy, None, None]: - for pl in range(len(self.game.players)): - for st in range(deref(self.support).NumStrategiesPlayer(pl+1)): - yield Strategy.wrap(deref(self.support).GetStrategy(pl+1, st+1)) + for player in deref(self.profile).GetGame().deref().GetPlayers(): + for strat in deref(self.profile).GetStrategies(player): + yield Strategy.wrap(strat) + + def __getitem__(self, player: PlayerReference) -> StrategySupport: + """Return a `StrategySupport` representing the strategies in the support + belonging to `player`. + + Parameters + ---------- + player : Player + The player to extract the support for + + Raises + ------ + MismatchError + If `player` is a `Player` from a different game. + """ + return StrategySupport( + self, + cython.cast(Player, self.game._resolve_player(player, "__getitem__")) + ) def __and__(self, other: StrategySupportProfile) -> StrategySupportProfile: + """Operator version of set intersection on support profiles. + + See also + -------- + intersection + """ return self.intersection(other) def __or__(self, other: StrategySupportProfile) -> StrategySupportProfile: + """Operator version of set union on support profiles. + + See also + -------- + union + """ return self.union(other) def __sub__(self, other: StrategySupportProfile) -> StrategySupportProfile: + """Operator version of set difference on support profiles. + + See also + -------- + difference + """ return self.difference(other) def remove(self, strategy: Strategy) -> StrategySupportProfile: @@ -124,13 +162,15 @@ class StrategySupportProfile: raise MismatchError( "remove(): strategy is not part of the game on which the profile is defined." ) - if deref(self.support).NumStrategiesPlayer(strategy.player.number + 1) == 1: + if deref(self.profile).GetStrategies( + cython.cast(Player, strategy.player).player + ).size() == 1: raise UndefinedOperationError( "remove(): cannot remove last strategy of a player" ) strategies = list(self) strategies.remove(strategy) - return StrategySupportProfile.wrap(self.game, strategies) + return self.game.strategy_support_profile(lambda x: x in strategies) def difference(self, other: StrategySupportProfile) -> StrategySupportProfile: """Create a support profile which contains all strategies in this profile that @@ -153,7 +193,7 @@ class StrategySupportProfile: """ if self.game != other.game: raise MismatchError("difference(): support profiles are defined on different games") - return StrategySupportProfile.wrap(self.game, set(self) - set(other)) + return self.game.strategy_support_profile(lambda x: x in set(self) - set(other)) def intersection(self, other: StrategySupportProfile) -> StrategySupportProfile: """Create a support profile which contains all strategies that are in both this and @@ -176,7 +216,7 @@ class StrategySupportProfile: """ if self.game != other.game: raise MismatchError("intersection(): support profiles are defined on different games") - return StrategySupportProfile.wrap(self.game, set(self) & set(other)) + return self.game.strategy_support_profile(lambda x: x in set(self) & set(other)) def union(self, other: StrategySupportProfile) -> StrategySupportProfile: """Create a support profile which contains all strategies that are in either this or @@ -199,7 +239,7 @@ class StrategySupportProfile: """ if self.game != other.game: raise MismatchError("union(): support profiles are defined on different games") - return StrategySupportProfile.wrap(self.game, set(self) | set(other)) + return self.game.strategy_support_profile(lambda x: x in set(self) | set(other)) def issubset(self, other: StrategySupportProfile) -> bool: """Test for whether this support is contained in another. @@ -221,7 +261,7 @@ class StrategySupportProfile: """ if self.game != other.game: raise MismatchError("issubset(): support profiles are defined on different games") - return deref(self.support).IsSubsetOf(deref(other.support)) + return deref(self.profile).IsSubsetOf(deref(other.profile)) def issuperset(self, other: StrategySupportProfile) -> bool: """Test for whether another support is contained in this one. @@ -253,14 +293,18 @@ class StrategySupportProfile: In 16.0.x, this returned a `StrategicRestriction` object. Strategic restrictions have been removed in favor of using deep copies of games. """ - return Game.parse_game(WriteGame(deref(self.support)).decode("ascii")) + with io.StringIO(WriteNfgFileSupport(deref(self.profile)).decode()) as f: + return read_nfg(f) + + def is_dominated(self, strategy: Strategy, strict: bool, external: bool = False) -> bool: + return deref(self.profile).IsDominated(strategy.strategy, strict, external) def _undominated_strategies_solve( profile: StrategySupportProfile, strict: bool, external: bool ) -> StrategySupportProfile: - result = StrategySupportProfile.wrap(profile.game) - result.support.reset( - new c_StrategySupportProfile(deref(profile.support).Undominated(strict, external)) + return StrategySupportProfile.wrap( + make_shared[c_StrategySupportProfile]( + deref(profile.profile).Undominated(strict, external) + ) ) - return result diff --git a/src/pygambit/supports.py b/src/pygambit/supports.py index c6919f0f1..6d63d7a40 100644 --- a/src/pygambit/supports.py +++ b/src/pygambit/supports.py @@ -20,12 +20,14 @@ # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # +from __future__ import annotations + import pygambit as gbt import pygambit.gambit as libgbt def undominated_strategies_solve( - profile: gbt.StrategySupportProfile, + profile: gbt.Game | gbt.StrategySupportProfile, strict: bool = False, external: bool = False ) -> gbt.StrategySupportProfile: @@ -36,8 +38,9 @@ def undominated_strategies_solve( Parameters ---------- - profile: StrategySupportProfile - The initial profile of strategies + profile : Game or StrategySupportProfile + The initial profile of strategies. If a `Game` is passed, elimination begins with + the full set of strategies on the game. strict : bool, default False If specified `True`, eliminate only strategies which are strictly dominated. @@ -53,4 +56,6 @@ def undominated_strategies_solve( StrategySupportProfile A new support profile containing only the strategies which are not dominated. """ + if isinstance(profile, gbt.Game): + profile = profile.strategy_support_profile() return libgbt._undominated_strategies_solve(profile, strict, external) diff --git a/src/pygambit/util.h b/src/pygambit/util.h index dffe1ce83..233aa0c03 100644 --- a/src/pygambit/util.h +++ b/src/pygambit/util.h @@ -36,51 +36,59 @@ using namespace std; using namespace Gambit; using namespace Gambit::Nash; -inline Game NewTable(Array *dim) { return NewTable(*dim); } +Game ParseGbtGame(std::string const &s) +{ + std::istringstream f(s); + return ReadGbtFile(f); +} -Game ReadGame(char *fn) +Game ParseEfgGame(std::string const &s) { - std::ifstream f(fn); - return ReadGame(f); + std::istringstream f(s); + return ReadEfgFile(f); } -Game ParseGame(char *s) +Game ParseNfgGame(std::string const &s) { std::istringstream f(s); - return ReadGame(f); + return ReadNfgFile(f); } -std::string WriteGame(const Game &p_game, const std::string &p_format) +Game ParseAggGame(std::string const &s) { - if (p_format == "html") { - return HTMLGameWriter().Write(p_game); - } - else if (p_format == "sgame") { - return LaTeXGameWriter().Write(p_game); - } - else if (p_format == "native" || p_format == "nfg" || p_format == "efg") { - std::ostringstream f; - p_game->Write(f, p_format); - return f.str(); - } - else { - throw ValueException("Unknown game save file format '" + p_format + "'"); - } + std::istringstream f(s); + return ReadAggFile(f); } -std::string WriteGame(const StrategySupportProfile &p_support) +std::string WriteEfgFile(const Game &p_game) { std::ostringstream f; - p_support.WriteNfgFile(f); + p_game->WriteEfgFile(f); return f.str(); } -// Create a copy on the heap (via new) of the element at index p_index of -// container p_container. -template