From 777523b66decb0739e4991a82bf0690fb41cbe13 Mon Sep 17 00:00:00 2001 From: Ted Turocy Date: Tue, 16 Apr 2024 12:47:52 +0100 Subject: [PATCH 01/99] Refactor build of C++ for pygambit extension. This refactors the build of the pygambit extension to create more granular libraries of the C++ code, with the aim of reducing unnecessary re-builds when changes are made. --- setup.py | 73 ++++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 58 insertions(+), 15 deletions(-) diff --git a/setup.py b/setup.py index e926c08f3..e69a70839 100644 --- a/setup.py +++ b/setup.py @@ -30,25 +30,66 @@ # with passing C++-specific flags when building the extension lrslib = ("lrslib", {"sources": glob.glob("src/solvers/lrs/*.c")}) -cppgambit = ( - "cppgambit", +cppgambit_include_dirs = ["src"] +cppgambit_cflags = ( + ["-std=c++17"] if platform.system() == "Darwin" + else ["/std:c++17"] if platform.system() == "Windows" + else [] +) + +cppgambit_core = ( + "cppgambit_core", + { + "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/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/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"]) + + libgambit = setuptools.Extension( "pygambit.gambit", sources=["src/pygambit/gambit.pyx"], @@ -101,7 +142,9 @@ def readme(): "scipy", "deprecated", ], - libraries=[cppgambit, lrslib], + libraries=[cppgambit_bimatrix, cppgambit_liap, cppgambit_logit, cppgambit_simpdiv, + cppgambit_gtracer, + cppgambit_games, cppgambit_core, lrslib], package_dir={"": "src"}, packages=["pygambit"], ext_modules=Cython.Build.cythonize(libgambit, From 8c56f2ec44c920c6091a12275844c5e6c1d7ca5d Mon Sep 17 00:00:00 2001 From: Ted Turocy Date: Thu, 11 Apr 2024 13:19:40 +0100 Subject: [PATCH 02/99] Implement estimation of agent LQRE in pygambit. This adds maximum-likelihood estimation of agent logit QRE, in parallel to the methods for strategic logit QRE. Function names for estimation for the strategic version have been modified to give a parallel naming scheme for both. --- ChangeLog | 8 ++ doc/pygambit.api.rst | 8 +- doc/pygambit.user.rst | 4 +- src/games/behavmixed.cc | 19 +++- src/pygambit/gambit.pxd | 32 +++++-- src/pygambit/nash.h | 39 ++++++-- src/pygambit/nash.pxi | 93 +++++++++++++++--- src/pygambit/nash.py | 3 +- src/pygambit/qre.py | 176 ++++++++++++++++++++++++++++++---- src/solvers/logit/efglogit.cc | 160 ++++++++++++++++++++++++++++++- src/solvers/logit/efglogit.h | 36 ++++++- src/solvers/logit/nfglogit.h | 12 +++ 12 files changed, 529 insertions(+), 61 deletions(-) diff --git a/ChangeLog b/ChangeLog index 11d30407a..1c0f86b05 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,13 @@ # Changelog +## [16.3.0] - unreleased + +### 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. + + ## [16.2.0] - unreleased ### Fixed diff --git a/doc/pygambit.api.rst b/doc/pygambit.api.rst index e1a8a1710..41136f67a 100644 --- a/doc/pygambit.api.rst +++ b/doc/pygambit.api.rst @@ -296,6 +296,10 @@ Computation of quantal response equilibria .. autosummary:: :toctree: api/ - fit_empirical - fit_fixedpoint + fit_strategy_empirical + fit_strategy_fixedpoint LogitQREMixedStrategyFitResult + + fit_behavior_empirical + fit_behavior_fixedpoint + LogitQREMixedBehaviorFitResult diff --git a/doc/pygambit.user.rst b/doc/pygambit.user.rst index 23574f6bc..7bf5be199 100644 --- a/doc/pygambit.user.rst +++ b/doc/pygambit.user.rst @@ -704,11 +704,11 @@ analysed in [McKPal95]_ using QRE. ) data = g.mixed_strategy_profile([[128*0.527, 128*(1-0.527)], [128*0.366, 128*(1-0.366)]]) -Estimation of QRE is done using :py:func:`.fit_fixedpoint`. +Estimation of QRE in the strategic form is done using :py:func:`.fit_strategy_fixedpoint`. .. ipython:: python - fit = gbt.qre.fit_fixedpoint(data) + fit = gbt.qre.fit_strategy_fixedpoint(data) The returned :py:class:`.LogitQREMixedStrategyFitResult` object contains the results of the estimation. diff --git a/src/games/behavmixed.cc b/src/games/behavmixed.cc index 640e679b0..cacb3b128 100644 --- a/src/games/behavmixed.cc +++ b/src/games/behavmixed.cc @@ -136,12 +136,21 @@ 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; } diff --git a/src/pygambit/gambit.pxd b/src/pygambit/gambit.pxd index 86ede77cc..76321976d 100644 --- a/src/pygambit/gambit.pxd +++ b/src/pygambit/gambit.pxd @@ -455,13 +455,17 @@ cdef extern from "solvers/gnm/gnm.h": 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/logit/efglogit.h": + cdef cppclass c_LogitQREMixedBehaviorProfile "LogitQREMixedBehaviorProfile": + c_LogitQREMixedBehaviorProfile(c_Game) except + + c_LogitQREMixedBehaviorProfile(c_LogitQREMixedBehaviorProfile) except + + c_Game GetGame() except + + c_MixedBehaviorProfileDouble GetProfile() # except + doesn't compile + double GetLambda() except + + double GetLogLike() except + + int BehaviorProfileLength() except + + double getitem "operator[]"(int) except +IndexError + c_List[c_MixedBehaviorProfileDouble] LogitBehaviorSolve(c_Game, double, double, @@ -484,11 +488,23 @@ cdef extern from "solvers/logit/nfglogit.h": c_MixedStrategyProfileDouble, double, double, double) except +RuntimeError + c_List[c_MixedStrategyProfileDouble] LogitStrategySolve(c_Game, + double, + double, + double) except +RuntimeError + + cdef extern from "nash.h": - shared_ptr[c_LogitQREMixedStrategyProfile] _logit_estimate "logit_estimate"( + shared_ptr[c_LogitQREMixedBehaviorProfile] LogitBehaviorEstimateHelper( + shared_ptr[c_MixedBehaviorProfileDouble], double, double + ) except + + shared_ptr[c_LogitQREMixedBehaviorProfile] LogitBehaviorAtLambdaHelper( + c_Game, double, double, double + ) except + + shared_ptr[c_LogitQREMixedStrategyProfile] LogitStrategyEstimateHelper( shared_ptr[c_MixedStrategyProfileDouble], double, double ) except + - shared_ptr[c_LogitQREMixedStrategyProfile] _logit_atlambda "logit_atlambda"( + shared_ptr[c_LogitQREMixedStrategyProfile] LogitStrategyAtLambdaHelper( c_Game, double, double, double ) except + c_List[c_LogitQREMixedStrategyProfile] _logit_principal_branch "logit_principal_branch"( diff --git a/src/pygambit/nash.h b/src/pygambit/nash.h index f4d61949f..0782c9daa 100644 --- a/src/pygambit/nash.h +++ b/src/pygambit/nash.h @@ -21,6 +21,7 @@ // #include "gambit.h" +#include "solvers/logit/efglogit.h" #include "solvers/logit/nfglogit.h" using namespace std; @@ -31,23 +32,41 @@ class NullBuffer : public std::streambuf { int overflow(int c) { return c; } }; -std::shared_ptr -logit_estimate(std::shared_ptr> p_frequencies, double p_firstStep, - double p_maxAccel) +std::shared_ptr +LogitBehaviorEstimateHelper(std::shared_ptr> p_frequencies, + double p_firstStep, double p_maxAccel) { - LogitQREMixedStrategyProfile start(p_frequencies->GetGame()); - StrategicQREEstimator alg; + return make_shared( + LogitBehaviorEstimate(*p_frequencies, p_firstStep, p_maxAccel)); +} + +std::shared_ptr LogitBehaviorAtLambdaHelper(const Game &p_game, + double p_lambda, + double p_firstStep, + double p_maxAccel) +{ + LogitQREMixedBehaviorProfile start(p_game); + AgentQREPathTracer 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); + return make_shared( + alg.SolveAtLambda(start, null_stream, p_lambda, 1.0)); +} + +std::shared_ptr +LogitStrategyEstimateHelper(std::shared_ptr> p_frequencies, + double p_firstStep, double p_maxAccel) +{ + return make_shared( + LogitStrategyEstimate(*p_frequencies, p_firstStep, p_maxAccel)); } -std::shared_ptr logit_atlambda(const Game &p_game, double p_lambda, - double p_firstStep, double p_maxAccel) +std::shared_ptr LogitStrategyAtLambdaHelper(const Game &p_game, + double p_lambda, + double p_firstStep, + double p_maxAccel) { LogitQREMixedStrategyProfile start(p_game); StrategicQREPathTracer alg; diff --git a/src/pygambit/nash.pxi b/src/pygambit/nash.pxi index 135f9e93e..92c0d1135 100644 --- a/src/pygambit/nash.pxi +++ b/src/pygambit/nash.pxi @@ -240,26 +240,26 @@ class LogitQREMixedStrategyProfile: return profile -def logit_estimate(profile: MixedStrategyProfileDouble, - first_step: float = .03, - max_accel: float = 1.1) -> LogitQREMixedStrategyProfile: +def _logit_strategy_estimate(profile: MixedStrategyProfileDouble, + first_step: float = .03, + max_accel: float = 1.1) -> LogitQREMixedStrategyProfile: """Estimate QRE corresponding to mixed strategy profile using maximum likelihood along the principal branch. """ - ret = LogitQREMixedStrategyProfile() - ret.thisptr = _logit_estimate(profile.profile, first_step, max_accel) + ret = LogitQREMixedStrategyProfile(profile.game) + ret.thisptr = LogitStrategyEstimateHelper(profile.profile, first_step, max_accel) return ret -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_atlambda(game: Game, + lam: float, + first_step: float = .03, + max_accel: float = 1.1) -> LogitQREMixedStrategyProfile: + """Compute the first QRE encountered along the principal branch of the strategic + game corresponding to lambda value `lam`. """ ret = LogitQREMixedStrategyProfile() - ret.thisptr = _logit_atlambda(game.game, lam, first_step, max_accel) + ret.thisptr = LogitStrategyAtLambdaHelper(game.game, lam, first_step, max_accel) return ret @@ -271,3 +271,72 @@ def logit_principal_branch(game: Game, first_step: float = .03, max_accel: float p.thisptr = copyitem_list_qrem(solns, i+1) ret.append(p) return ret + + +@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).BehaviorProfileLength() + + 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_MixedBehaviorProfileDouble](deref(self.thisptr).GetProfile()) + ) + return profile + + +def _logit_behavior_estimate(profile: MixedBehaviorProfileDouble, + 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 = LogitBehaviorEstimateHelper(profile.profile, first_step, max_accel) + return ret + + +def logit_behavior_atlambda(game: Game, + lam: float, + first_step: float = .03, + max_accel: float = 1.1) -> LogitQREMixedBehaviorProfile: + """Compute the first QRE encountered along the principal branch of the extensive + game corresponding to lambda value `lam`. + """ + ret = LogitQREMixedBehaviorProfile() + ret.thisptr = LogitBehaviorAtLambdaHelper(game.game, lam, first_step, max_accel) + return ret diff --git a/src/pygambit/nash.py b/src/pygambit/nash.py index cbd32daf3..a96da66d3 100644 --- a/src/pygambit/nash.py +++ b/src/pygambit/nash.py @@ -587,5 +587,6 @@ def logit_solve( ) -logit_atlambda = libgbt.logit_atlambda +logit_behavior_atlambda = libgbt.logit_behavior_atlambda +logit_strategy_atlambda = libgbt.logit_strategy_atlambda logit_principal_branch = libgbt.logit_principal_branch diff --git a/src/pygambit/qre.py b/src/pygambit/qre.py index df2f6661b..ec46c3845 100644 --- a/src/pygambit/qre.py +++ b/src/pygambit/qre.py @@ -28,7 +28,9 @@ import numpy import scipy.optimize -from . import gambit, pctrace +import pygambit.gambit as libgbt + +from . import pctrace from .profiles import Solution @@ -299,8 +301,8 @@ class LogitQREMixedStrategyFitResult: See Also -------- - fit_fixedpoint - fit_empirical + fit_strategy_fixedpoint + fit_strategy_empirical """ def __init__(self, data, method, lam, profile, log_like): self._data = data @@ -315,7 +317,7 @@ def method(self) -> str: return self._method @property - def data(self) -> gambit.MixedStrategyProfileDouble: + def data(self) -> libgbt.MixedStrategyProfileDouble: """The empirical strategy frequencies used to estimate the QRE.""" return self._data @@ -325,7 +327,7 @@ def lam(self) -> float: return self._lam @property - def profile(self) -> gambit.MixedStrategyProfileDouble: + def profile(self) -> libgbt.MixedStrategyProfileDouble: """The mixed strategy profile corresponding to the QRE.""" return self._profile @@ -334,21 +336,23 @@ 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 fit_strategy_fixedpoint( + data: libgbt.MixedStrategyProfileDouble ) -> LogitQREMixedStrategyFitResult: """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]_ - .. versionadded:: 16.1.0 + .. versionchanged:: 16.2.0 + + Renamed from `fit_fixedpoint` to disambiguate from agent version Parameters ---------- @@ -366,8 +370,8 @@ def fit_fixedpoint( See Also -------- - fit_empirical : Estimate QRE by approximation of the correspondence - using independent decision problems. + fit_strategy_empirical : Estimate QRE by approximation of the correspondence + using independent decision problems. References ---------- @@ -375,14 +379,14 @@ def fit_fixedpoint( as a structural model for estimation: The missing manual. SSRN working paper 4425515. """ - res = gambit.logit_estimate(data) + res = libgbt._logit_strategy_estimate(data) return LogitQREMixedStrategyFitResult( data, "fixedpoint", res.lam, res.profile, res.log_like ) -def fit_empirical( - data: gambit.MixedStrategyProfileDouble +def fit_strategy_empirical( + data: libgbt.MixedStrategyProfileDouble ) -> LogitQREMixedStrategyFitResult: """Use maximum likelihood estimation to estimate a quantal response equilibrium using the empirical payoff method. @@ -390,7 +394,9 @@ def fit_empirical( considerations of the QRE and approximates instead by a collection of independent decision problems. [1]_ - .. versionadded:: 16.1.0 + .. versionchanged:: 16.2.0 + + Renamed from `fit_empirical` to disambiguate from agent version Returns ------- @@ -400,7 +406,7 @@ def fit_empirical( See Also -------- - fit_fixedpoint : Estimate QRE precisely by computing the correspondence + fit_strategy_fixedpoint : Estimate QRE precisely by computing the correspondence References ---------- @@ -429,3 +435,141 @@ def log_like(lam: float) -> float: return LogitQREMixedStrategyFitResult( data, "empirical", res.x[0], do_logit(res.x[0]), -res.fun ) + + +class LogitQREMixedBehaviorFitResult: + """The result of fitting a QRE to a given probability distribution + over actions. + + See Also + -------- + fit_behavior_fixedpoint + """ + 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 + + @property + def method(self) -> str: + """The method used to estimate the QRE; either "fixedpoint" or "empirical".""" + return self._method + + @property + def data(self) -> libgbt.MixedBehaviorProfileDouble: + """The empirical actions frequencies used to estimate the QRE.""" + return self._data + + @property + def lam(self) -> float: + """The value of lambda corresponding to the QRE.""" + return self._lam + + @property + def profile(self) -> libgbt.MixedBehaviorProfileDouble: + """The mixed behavior 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 + + def __repr__(self) -> str: + return ( + f"" + ) + + +def fit_behavior_fixedpoint( + data: libgbt.MixedBehaviorProfileDouble +) -> LogitQREMixedBehaviorFitResult: + """Use maximum likelihood estimation to find the logit quantal + response equilibrium on the principal branch for an extensive game + which best fits empirical frequencies of play. [1]_ + + .. versionadded:: 16.2.0 + + Parameters + ---------- + data : MixedBehaviorProfileDouble + 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 action + rather than probabilities. + + Returns + ------- + LogitQREMixedBehaviorFitResult + The result of the estimation represented as a + ``LogitQREMixedBehaviorFitResult`` object. + + See Also + -------- + fit_strategy_fixedpoint : Estimate QRE using the strategic representation + fit_behavior_empirical : Estimate QRE by approximation of the correspondence + using independent decision problems. + + 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 = libgbt._logit_behavior_estimate(data) + return LogitQREMixedBehaviorFitResult( + data, "fixedpoint", res.lam, res.profile, res.log_like + ) + + +def fit_behavior_empirical( + data: libgbt.MixedBehaviorProfileDouble +) -> LogitQREMixedBehaviorFitResult: + """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]_ + + Returns + ------- + LogitQREMixedBehaviorFitResult + The result of the estimation represented as a + ``LogitQREMixedBehaviorFitResult`` object. + + See Also + -------- + fit_behavior_fixedpoint : Estimate QRE precisely by computing the correspondence + + 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. + """ + def do_logit(lam: float): + logit_probs = [[math.exp(lam*a) for a in infoset] + for player in values for infoset in player] + 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 infoset in logit_probs for v in infoset] + 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[a] for p in data.game.players for s in p.infosets for a in s.actions] + normalized = data.normalize() + values = [[[normalized.action_value(a) for a in s.actions] + for s in p.infosets] + for p in data.game.players] + res = scipy.optimize.minimize(lambda x: -log_like(x[0]), (0.1,), + bounds=((0.0, None),)) + return LogitQREMixedBehaviorFitResult( + data, "empirical", res.x[0], do_logit(res.x[0]), -res.fun + ) diff --git a/src/solvers/logit/efglogit.cc b/src/solvers/logit/efglogit.cc index 46b99b3d1..3daf967b6 100644 --- a/src/solvers/logit/efglogit.cc +++ b/src/solvers/logit/efglogit.cc @@ -287,7 +287,7 @@ void AgentQREPathTracer::CallbackFunction::operator()(const Vector &p_po for (int i = 1; i < p_point.Length(); i++) { profile[i] = exp(p_point[i]); } - m_profiles.push_back(LogitQREMixedBehaviorProfile(profile, p_point.back())); + m_profiles.push_back(LogitQREMixedBehaviorProfile(profile, p_point.back(), 0.0)); } //------------------------------------------------------------------------------ @@ -300,7 +300,7 @@ class AgentQREPathTracer::LambdaCriterion : public PathTracer::CriterionFunction double operator()(const Vector &p_point, const Vector &p_tangent) const override { - return p_point[p_point.Length()] - m_lambda; + return p_point.back() - m_lambda; } private: @@ -372,4 +372,160 @@ AgentQREPathTracer::SolveAtLambda(const LogitQREMixedBehaviorProfile &p_start, return func.GetProfiles().back(); } +//---------------------------------------------------------------------------- +// AgentQREEstimator: Criterion function +//---------------------------------------------------------------------------- + +namespace { +double LogLike(const Vector &p_frequencies, const Vector &p_point) +{ + double logL = 0.0; + for (int i = 1; i <= p_frequencies.Length(); i++) { + logL += p_frequencies[i] * log(p_point[i]); + } + return logL; +} + +} // end anonymous namespace + +class AgentQREEstimator::CriterionFunction : public PathTracer::CriterionFunction { +public: + explicit CriterionFunction(const Vector &p_frequencies) : m_frequencies(p_frequencies) {} + ~CriterionFunction() override = default; + + double operator()(const Vector &p_point, const Vector &p_tangent) const override + { + double diff_logL = 0.0; + for (int i = 1; i <= m_frequencies.Length(); i++) { + diff_logL += m_frequencies[i] * p_tangent[i]; + } + return diff_logL; + } + +private: + Vector m_frequencies; +}; + +//---------------------------------------------------------------------------- +// AgentQREEstimator: Callback function +//---------------------------------------------------------------------------- + +class AgentQREEstimator::CallbackFunction : public PathTracer::CallbackFunction { +public: + CallbackFunction(std::ostream &p_stream, const Game &p_game, const Vector &p_frequencies, + bool p_fullGraph, int p_decimals); + ~CallbackFunction() override = default; + + void operator()(const Vector &p_point, bool p_isTerminal) const override; + + LogitQREMixedBehaviorProfile GetMaximizer() const + { + return {m_bestProfile, m_bestLambda, m_maxlogL}; + } + void PrintMaximizer() const; + +private: + void PrintProfile(const MixedBehaviorProfile &, double) const; + + std::ostream &m_stream; + Game m_game; + const Vector &m_frequencies; + bool m_fullGraph; + int m_decimals; + mutable MixedBehaviorProfile m_bestProfile; + mutable double m_bestLambda{0.0}; + mutable double m_maxlogL; +}; + +AgentQREEstimator::CallbackFunction::CallbackFunction(std::ostream &p_stream, const Game &p_game, + const Vector &p_frequencies, + bool p_fullGraph, int p_decimals) + : m_stream(p_stream), m_game(p_game), m_frequencies(p_frequencies), m_fullGraph(p_fullGraph), + m_decimals(p_decimals), m_bestProfile(p_game), + m_maxlogL(LogLike(p_frequencies, static_cast &>(m_bestProfile))) +{ +} + +void AgentQREEstimator::CallbackFunction::PrintProfile( + const MixedBehaviorProfile &p_profile, double p_logL) const +{ + for (size_t i = 1; i <= p_profile.BehaviorProfileLength(); i++) { + m_stream << "," << std::setprecision(m_decimals) << p_profile[i]; + } + m_stream.setf(std::ios::fixed); + m_stream << "," << std::setprecision(m_decimals); + m_stream << p_logL; + m_stream.unsetf(std::ios::fixed); +} + +void AgentQREEstimator::CallbackFunction::PrintMaximizer() const +{ + m_stream.setf(std::ios::fixed); + // By convention, we output lambda first + m_stream << std::setprecision(m_decimals) << m_bestLambda; + m_stream.unsetf(std::ios::fixed); + PrintProfile(m_bestProfile, m_maxlogL); + m_stream << std::endl; +} + +void AgentQREEstimator::CallbackFunction::operator()(const Vector &x, + bool p_isTerminal) const +{ + m_stream.setf(std::ios::fixed); + // By convention, we output lambda first + if (!p_isTerminal) { + m_stream << std::setprecision(m_decimals) << x[x.Length()]; + } + else { + m_stream << "NE"; + } + m_stream.unsetf(std::ios::fixed); + MixedBehaviorProfile profile(m_game); + for (int i = 1; i < x.Length(); i++) { + profile[i] = exp(x[i]); + } + double logL = LogLike(m_frequencies, static_cast &>(profile)); + PrintProfile(profile, logL); + m_stream << std::endl; + if (logL > m_maxlogL) { + m_maxlogL = logL; + m_bestLambda = x[x.Length()]; + m_bestProfile = profile; + } +} + +//---------------------------------------------------------------------------- +// AgentQREEstimator: Main driver routine +//---------------------------------------------------------------------------- + +LogitQREMixedBehaviorProfile +AgentQREEstimator::Estimate(const LogitQREMixedBehaviorProfile &p_start, + const MixedBehaviorProfile &p_frequencies, + std::ostream &p_stream, double p_maxLambda, double p_omega) +{ + if (p_start.GetGame() != p_frequencies.GetGame()) { + throw MismatchException(); + } + + Vector x(p_start.BehaviorProfileLength() + 1); + for (int i = 1; i <= p_start.BehaviorProfileLength(); i++) { + x[i] = log(p_start[i]); + } + x.back() = p_start.GetLambda(); + + CallbackFunction callback(p_stream, p_start.GetGame(), + static_cast &>(p_frequencies), m_fullGraph, + m_decimals); + while (x.back() < p_maxLambda) { + TracePath( + EquationSystem(p_start.GetGame()), x, p_omega, + [p_maxLambda](const Vector &p_point) { + return LambdaRangeTerminationFunction(p_point, 0, p_maxLambda); + }, + callback, CriterionFunction(static_cast &>(p_frequencies))); + } + callback.PrintMaximizer(); + return callback.GetMaximizer(); +} + } // end namespace Gambit diff --git a/src/solvers/logit/efglogit.h b/src/solvers/logit/efglogit.h index 1f759b519..67e567da7 100644 --- a/src/solvers/logit/efglogit.h +++ b/src/solvers/logit/efglogit.h @@ -31,12 +31,14 @@ namespace Gambit { class LogitQREMixedBehaviorProfile { public: explicit LogitQREMixedBehaviorProfile(const Game &p_game) : m_profile(p_game), m_lambda(0.0) {} - LogitQREMixedBehaviorProfile(const MixedBehaviorProfile &p_profile, double p_lambda) - : m_profile(p_profile), m_lambda(p_lambda) + LogitQREMixedBehaviorProfile(const MixedBehaviorProfile &p_profile, double p_lambda, + double p_logLike) + : m_profile(p_profile), m_lambda(p_lambda), m_logLike(p_logLike) { } double GetLambda() const { return m_lambda; } const MixedBehaviorProfile &GetProfile() const { return m_profile; } + double GetLogLike() const { return m_logLike; } Game GetGame() const { return m_profile.GetGame(); } size_t BehaviorProfileLength() const { return m_profile.BehaviorProfileLength(); } @@ -45,6 +47,7 @@ class LogitQREMixedBehaviorProfile { private: const MixedBehaviorProfile m_profile; double m_lambda; + double m_logLike; }; class AgentQREPathTracer : public PathTracer { @@ -65,7 +68,7 @@ class AgentQREPathTracer : public PathTracer { void SetDecimals(int p_decimals) { m_decimals = p_decimals; } int GetDecimals() const { return m_decimals; } -private: +protected: bool m_fullGraph; int m_decimals; @@ -74,6 +77,33 @@ class AgentQREPathTracer : public PathTracer { class LambdaCriterion; }; +class AgentQREEstimator : public AgentQREPathTracer { +public: + AgentQREEstimator() = default; + ~AgentQREEstimator() override = default; + + LogitQREMixedBehaviorProfile Estimate(const LogitQREMixedBehaviorProfile &p_start, + const MixedBehaviorProfile &p_frequencies, + std::ostream &p_logStream, double p_maxLambda, + double p_omega); + +protected: + class CriterionFunction; + class CallbackFunction; +}; + +inline LogitQREMixedBehaviorProfile +LogitBehaviorEstimate(const MixedBehaviorProfile &p_frequencies, double p_firstStep, + double p_maxAccel) +{ + LogitQREMixedBehaviorProfile start(p_frequencies.GetGame()); + AgentQREEstimator alg; + alg.SetMaxDecel(p_maxAccel); + alg.SetStepsize(p_firstStep); + std::ostringstream ostream; + return alg.Estimate(start, p_frequencies, ostream, 2.0, 1.0); +} + inline List> LogitBehaviorSolve(const Game &p_game, double p_epsilon, double p_firstStep, double p_maxAccel) { diff --git a/src/solvers/logit/nfglogit.h b/src/solvers/logit/nfglogit.h index 056d0e897..da704b28f 100644 --- a/src/solvers/logit/nfglogit.h +++ b/src/solvers/logit/nfglogit.h @@ -103,6 +103,18 @@ class StrategicQREEstimator : public StrategicQREPathTracer { class CallbackFunction; }; +inline LogitQREMixedStrategyProfile +LogitStrategyEstimate(const MixedStrategyProfile &p_frequencies, double p_firstStep, + double p_maxAccel) +{ + LogitQREMixedStrategyProfile start(p_frequencies.GetGame()); + StrategicQREEstimator alg; + alg.SetMaxDecel(p_maxAccel); + alg.SetStepsize(p_firstStep); + std::ostringstream ostream; + return alg.Estimate(start, p_frequencies, ostream, 1000000.0, 1.0); +} + inline List> LogitStrategySolve(const Game &p_game, double p_regret, double p_firstStep, double p_maxAccel) { From 5708d2a66ef553935aaeca471f78c8e709c75247 Mon Sep 17 00:00:00 2001 From: Ted Turocy Date: Thu, 16 May 2024 14:29:34 +0100 Subject: [PATCH 03/99] Refactor implementation of path-following and QRE. This is a substantial refactoring and cleanup of path-following and the interface to QRE tracing. The only new functionality is to implement the ability to stop at an interior local maximiser when fitting QRE. Otherwise, other changes are internal to simplify calling the various QRE-related routines and separate concerns (e.g. formatting/printing is no longer part of the QRE tracer itself but properly delegated to a provided observer function if that is desired). --- .devcontainer/devcontainer.json | 15 + ChangeLog | 3 +- Makefile.am | 3 +- src/pygambit/gambit.pxd | 46 ++-- src/pygambit/nash.h | 100 ++++--- src/pygambit/nash.pxi | 20 +- src/pygambit/qre.py | 20 +- src/solvers/logit/efglogit.cc | 470 +++++++++++++------------------- src/solvers/logit/efglogit.h | 126 --------- src/solvers/logit/logbehav.h | 81 ++---- src/solvers/logit/logbehav.imp | 297 ++++++-------------- src/solvers/logit/logit.h | 126 +++++++++ src/solvers/logit/nfglogit.cc | 449 ++++++++++++------------------ src/solvers/logit/nfglogit.h | 137 ---------- src/solvers/logit/path.cc | 21 +- src/solvers/logit/path.h | 78 ++---- src/tools/logit/logit.cc | 74 +++-- tests/test_nash.py | 27 +- 18 files changed, 812 insertions(+), 1281 deletions(-) create mode 100644 .devcontainer/devcontainer.json delete mode 100644 src/solvers/logit/efglogit.h create mode 100644 src/solvers/logit/logit.h delete mode 100644 src/solvers/logit/nfglogit.h 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/ChangeLog b/ChangeLog index 1c0f86b05..360a9368e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -5,7 +5,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. + parallel naming. Estimation now supports an option to stop at the first interior local + maxmizer found (if one exists). ## [16.2.0] - unreleased diff --git a/Makefile.am b/Makefile.am index 674c8ce32..d1d20f610 100644 --- a/Makefile.am +++ b/Makefile.am @@ -519,9 +519,8 @@ 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/logit/logit.cc diff --git a/src/pygambit/gambit.pxd b/src/pygambit/gambit.pxd index 76321976d..722c4ad5d 100644 --- a/src/pygambit/gambit.pxd +++ b/src/pygambit/gambit.pxd @@ -455,7 +455,7 @@ cdef extern from "solvers/gnm/gnm.h": int p_localNewtonInterval, int p_localNewtonMaxits ) except +RuntimeError -cdef extern from "solvers/logit/efglogit.h": +cdef extern from "solvers/logit/logit.h": cdef cppclass c_LogitQREMixedBehaviorProfile "LogitQREMixedBehaviorProfile": c_LogitQREMixedBehaviorProfile(c_Game) except + c_LogitQREMixedBehaviorProfile(c_LogitQREMixedBehaviorProfile) except + @@ -463,15 +463,9 @@ cdef extern from "solvers/logit/efglogit.h": c_MixedBehaviorProfileDouble GetProfile() # except + doesn't compile double GetLambda() except + double GetLogLike() except + - int BehaviorProfileLength() except + + int size() except + double getitem "operator[]"(int) except +IndexError - c_List[c_MixedBehaviorProfileDouble] LogitBehaviorSolve(c_Game, - double, - double, - double) except +RuntimeError - -cdef extern from "solvers/logit/nfglogit.h": cdef cppclass c_LogitQREMixedStrategyProfile "LogitQREMixedStrategyProfile": c_LogitQREMixedStrategyProfile(c_Game) except + c_LogitQREMixedStrategyProfile(c_LogitQREMixedStrategyProfile) except + @@ -479,34 +473,32 @@ cdef extern from "solvers/logit/nfglogit.h": c_MixedStrategyProfileDouble 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 - - c_List[c_MixedStrategyProfileDouble] LogitStrategySolve(c_Game, - double, - double, - double) except +RuntimeError - cdef extern from "nash.h": - shared_ptr[c_LogitQREMixedBehaviorProfile] LogitBehaviorEstimateHelper( - shared_ptr[c_MixedBehaviorProfileDouble], double, double + c_List[c_MixedBehaviorProfileDouble] LogitBehaviorSolveWrapper( + c_Game, double, double, double ) except + - shared_ptr[c_LogitQREMixedBehaviorProfile] LogitBehaviorAtLambdaHelper( + c_List[c_LogitQREMixedBehaviorProfile] LogitBehaviorPrincipalBranchWrapper( c_Game, double, double, double ) except + - shared_ptr[c_LogitQREMixedStrategyProfile] LogitStrategyEstimateHelper( - shared_ptr[c_MixedStrategyProfileDouble], double, double + shared_ptr[c_LogitQREMixedBehaviorProfile] LogitBehaviorAtLambdaWrapper( + c_Game, double, double, double + ) except + + shared_ptr[c_LogitQREMixedBehaviorProfile] LogitBehaviorEstimateWrapper( + shared_ptr[c_MixedBehaviorProfileDouble], bool, double, double ) except + - shared_ptr[c_LogitQREMixedStrategyProfile] LogitStrategyAtLambdaHelper( + c_List[c_MixedStrategyProfileDouble] LogitStrategySolveWrapper( c_Game, double, double, double ) except + - c_List[c_LogitQREMixedStrategyProfile] _logit_principal_branch "logit_principal_branch"( + c_List[c_LogitQREMixedStrategyProfile] LogitStrategyPrincipalBranchWrapper( c_Game, double, double, double ) except + + shared_ptr[c_LogitQREMixedStrategyProfile] LogitStrategyAtLambdaWrapper( + c_Game, double, double, double + ) except + + shared_ptr[c_LogitQREMixedStrategyProfile] LogitStrategyEstimateWrapper( + shared_ptr[c_MixedStrategyProfileDouble], bool, double, double + ) except + diff --git a/src/pygambit/nash.h b/src/pygambit/nash.h index 0782c9daa..666b275ee 100644 --- a/src/pygambit/nash.h +++ b/src/pygambit/nash.h @@ -21,71 +21,83 @@ // #include "gambit.h" -#include "solvers/logit/efglogit.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; +} + +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 -LogitBehaviorEstimateHelper(std::shared_ptr> p_frequencies, - double p_firstStep, double p_maxAccel) +LogitBehaviorEstimateWrapper(std::shared_ptr> p_frequencies, + bool p_stopAtLocal, double p_firstStep, double p_maxAccel) { - return make_shared( - LogitBehaviorEstimate(*p_frequencies, p_firstStep, p_maxAccel)); + return make_shared(LogitBehaviorEstimate( + *p_frequencies, 1000000.0, 1.0, p_stopAtLocal, p_firstStep, p_maxAccel)); } -std::shared_ptr LogitBehaviorAtLambdaHelper(const Game &p_game, - double p_lambda, - double p_firstStep, - double p_maxAccel) +std::shared_ptr LogitBehaviorAtLambdaWrapper(const Game &p_game, + double p_lambda, + double p_firstStep, + double p_maxAccel) { LogitQREMixedBehaviorProfile start(p_game); - AgentQREPathTracer 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)); + LogitBehaviorSolveLambda(start, p_lambda, 1.0, p_firstStep, p_maxAccel)); } -std::shared_ptr -LogitStrategyEstimateHelper(std::shared_ptr> p_frequencies, - double p_firstStep, double p_maxAccel) +List> LogitStrategySolveWrapper(const Game &p_game, double p_regret, + double p_firstStep, double p_maxAccel) { - return make_shared( - LogitStrategyEstimate(*p_frequencies, p_firstStep, p_maxAccel)); + 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); } -std::shared_ptr LogitStrategyAtLambdaHelper(const Game &p_game, - double p_lambda, - double p_firstStep, - double p_maxAccel) +std::shared_ptr LogitStrategyAtLambdaWrapper(const Game &p_game, + double p_lambda, + 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)); + LogitStrategySolveLambda(start, p_lambda, 1.0, p_firstStep, p_maxAccel)); } -List logit_principal_branch(const Game &p_game, double p_maxregret, - double p_firstStep, double p_maxAccel) +std::shared_ptr +LogitStrategyEstimateWrapper(std::shared_ptr> p_frequencies, + bool p_stopAtLocal, 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); + 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 92c0d1135..4192fd464 100644 --- a/src/pygambit/nash.pxi +++ b/src/pygambit/nash.pxi @@ -185,13 +185,13 @@ def _gnm_strategy_solve( 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 @@ -208,7 +208,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) @@ -241,13 +241,14 @@ class 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. """ ret = LogitQREMixedStrategyProfile(profile.game) - ret.thisptr = LogitStrategyEstimateHelper(profile.profile, first_step, max_accel) + ret.thisptr = LogitStrategyEstimateWrapper(profile.profile, local_max, first_step, max_accel) return ret @@ -259,12 +260,12 @@ def logit_strategy_atlambda(game: Game, game corresponding to lambda value `lam`. """ ret = LogitQREMixedStrategyProfile() - ret.thisptr = LogitStrategyAtLambdaHelper(game.game, lam, first_step, max_accel) + ret.thisptr = LogitStrategyAtLambdaWrapper(game.game, lam, first_step, max_accel) return ret 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) + solns = LogitStrategyPrincipalBranchWrapper(game.game, 1.0e-8, first_step, max_accel) ret = [] for i in range(solns.Length()): p = LogitQREMixedStrategyProfile() @@ -287,7 +288,7 @@ class LogitQREMixedBehaviorProfile: return f"LogitQREMixedBehaviorProfile(lam={self.lam},profile={self.profile})" def __len__(self): - return deref(self.thisptr).BehaviorProfileLength() + return deref(self.thisptr).size() def __getitem__(self, int i): return deref(self.thisptr).getitem(i+1) @@ -320,13 +321,14 @@ class LogitQREMixedBehaviorProfile: 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 = LogitBehaviorEstimateHelper(profile.profile, first_step, max_accel) + ret.thisptr = LogitBehaviorEstimateWrapper(profile.profile, local_max, first_step, max_accel) return ret @@ -338,5 +340,5 @@ def logit_behavior_atlambda(game: Game, game corresponding to lambda value `lam`. """ ret = LogitQREMixedBehaviorProfile() - ret.thisptr = LogitBehaviorAtLambdaHelper(game.game, lam, first_step, max_accel) + ret.thisptr = LogitBehaviorAtLambdaWrapper(game.game, lam, first_step, max_accel) return ret diff --git a/src/pygambit/qre.py b/src/pygambit/qre.py index ec46c3845..abdde76d5 100644 --- a/src/pygambit/qre.py +++ b/src/pygambit/qre.py @@ -344,7 +344,8 @@ def __repr__(self) -> str: def fit_strategy_fixedpoint( - data: libgbt.MixedStrategyProfileDouble + data: libgbt.MixedStrategyProfileDouble, + local_max: bool = False ) -> LogitQREMixedStrategyFitResult: """Use maximum likelihood estimation to find the logit quantal response equilibrium on the principal branch for a strategic game @@ -362,6 +363,13 @@ def fit_strategy_fixedpoint( be expressed as total counts of observations of each strategy rather than probabilities. + 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. + + .. versionadded:: 16.2.0 + Returns ------- LogitQREMixedStrategyFitResult @@ -379,7 +387,7 @@ def fit_strategy_fixedpoint( as a structural model for estimation: The missing manual. SSRN working paper 4425515. """ - res = libgbt._logit_strategy_estimate(data) + res = libgbt._logit_strategy_estimate(data, local_max=local_max) return LogitQREMixedStrategyFitResult( data, "fixedpoint", res.lam, res.profile, res.log_like ) @@ -485,7 +493,8 @@ def __repr__(self) -> str: def fit_behavior_fixedpoint( - data: libgbt.MixedBehaviorProfileDouble + data: libgbt.MixedBehaviorProfileDouble, + local_max: bool = False ) -> LogitQREMixedBehaviorFitResult: """Use maximum likelihood estimation to find the logit quantal response equilibrium on the principal branch for an extensive game @@ -501,6 +510,11 @@ def fit_behavior_fixedpoint( be expressed as total counts of observations of each action rather than probabilities. + 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. + Returns ------- LogitQREMixedBehaviorFitResult diff --git a/src/solvers/logit/efglogit.cc b/src/solvers/logit/efglogit.cc index 3daf967b6..f54a6facc 100644 --- a/src/solvers/logit/efglogit.cc +++ b/src/solvers/logit/efglogit.cc @@ -22,28 +22,75 @@ // #include -#include + #include "gambit.h" +#include "logit.h" #include "logbehav.imp" -#include "efglogit.h" +#include "path.h" -namespace Gambit { +namespace { + +double LogLike(const Vector &p_frequencies, const Vector &p_point) +{ + double logL = 0.0; + for (int i = 1; i <= p_frequencies.Length(); i++) { + logL += p_frequencies[i] * log(p_point[i]); + } + return logL; +} + +double DiffLogLike(const Vector &p_frequencies, const Vector &p_tangent) +{ + double diff_logL = 0.0; + for (int i = 1; i <= p_frequencies.Length(); i++) { + diff_logL += p_frequencies[i] * p_tangent[i]; + } + return diff_logL; +} + +MixedBehaviorProfile PointToProfile(const Game &p_game, const Vector &p_point) +{ + MixedBehaviorProfile profile(p_game); + for (int i = 1; i < p_point.Length(); i++) { + profile[i] = exp(p_point[i]); + } + return profile; +} + +LogBehavProfile PointToLogProfile(const Game &p_game, const Vector &p_point) +{ + LogBehavProfile profile(p_game); + for (int i = 1; i <= profile.BehaviorProfileLength(); i++) { + profile.SetLogProb(i, p_point[i]); + } + return profile; +} -//------------------------------------------------------------------------------ -// Classes representing equations -//------------------------------------------------------------------------------ +Vector ProfileToPoint(const LogitQREMixedBehaviorProfile &p_profile) +{ + Vector point(p_profile.size() + 1); + for (int i = 1; i <= p_profile.size(); i++) { + point[i] = log(p_profile[i]); + } + point.back() = p_profile.GetLambda(); + return point; +} + +bool RegretTerminationFunction(const Game &p_game, const Vector &p_point, double p_regret) +{ + return (p_point.back() < 0.0 || PointToProfile(p_game, p_point).GetMaxRegret() < p_regret); +} -class AgentQREPathTracer::EquationSystem : public PathTracer::EquationSystem { +class EquationSystem { public: explicit EquationSystem(const Game &p_game); - - ~EquationSystem() override; + ~EquationSystem(); // Compute the value of the system of equations at the specified point. - void GetValue(const Vector &p_point, Vector &p_lhs) const override; + void GetValue(const Vector &p_point, Vector &p_lhs) const; // Compute the Jacobian matrix at the specified point. - void GetJacobian(const Vector &p_point, Matrix &p_matrix) const override; + void GetJacobian(const Vector &p_point, Matrix &p_matrix) const; private: // @@ -110,7 +157,7 @@ class AgentQREPathTracer::EquationSystem : public PathTracer::EquationSystem { const Game &m_game; }; -AgentQREPathTracer::EquationSystem::EquationSystem(const Game &p_game) : m_game(p_game) +EquationSystem::EquationSystem(const Game &p_game) : m_game(p_game) { for (int pl = 1; pl <= m_game->NumPlayers(); pl++) { GamePlayer player = m_game->GetPlayer(pl); @@ -123,15 +170,15 @@ AgentQREPathTracer::EquationSystem::EquationSystem(const Game &p_game) : m_game( } } -AgentQREPathTracer::EquationSystem::~EquationSystem() +EquationSystem::~EquationSystem() { for (int i = 1; i <= m_equations.Length(); i++) { delete m_equations[i]; } } -double AgentQREPathTracer::EquationSystem::SumToOneEquation::Value( - const LogBehavProfile &p_profile, double p_lambda) const +double EquationSystem::SumToOneEquation::Value(const LogBehavProfile &p_profile, + double p_lambda) const { double value = -1.0; for (int act = 1; act <= m_infoset->NumActions(); act++) { @@ -140,8 +187,8 @@ double AgentQREPathTracer::EquationSystem::SumToOneEquation::Value( return value; } -void AgentQREPathTracer::EquationSystem::SumToOneEquation::Gradient( - const LogBehavProfile &p_profile, double p_lambda, Vector &p_gradient) const +void EquationSystem::SumToOneEquation::Gradient(const LogBehavProfile &p_profile, + double p_lambda, Vector &p_gradient) const { int i = 1; for (int pl = 1; pl <= m_game->NumPlayers(); pl++) { @@ -165,17 +212,16 @@ void AgentQREPathTracer::EquationSystem::SumToOneEquation::Gradient( p_gradient[i] = 0.0; } -double -AgentQREPathTracer::EquationSystem::RatioEquation::Value(const LogBehavProfile &p_profile, - double p_lambda) const +double EquationSystem::RatioEquation::Value(const LogBehavProfile &p_profile, + double p_lambda) const { return (p_profile.GetLogProb(m_pl, m_iset, m_act) - p_profile.GetLogProb(m_pl, m_iset, 1) - p_lambda * (p_profile.GetPayoff(m_infoset->GetAction(m_act)) - p_profile.GetPayoff(m_infoset->GetAction(1)))); } -void AgentQREPathTracer::EquationSystem::RatioEquation::Gradient( - const LogBehavProfile &p_profile, double p_lambda, Vector &p_gradient) const +void EquationSystem::RatioEquation::Gradient(const LogBehavProfile &p_profile, + double p_lambda, Vector &p_gradient) const { int i = 1; for (int pl = 1; pl <= m_game->NumPlayers(); pl++) { @@ -208,29 +254,19 @@ void AgentQREPathTracer::EquationSystem::RatioEquation::Gradient( p_profile.GetPayoff(m_infoset->GetAction(m_act))); } -void AgentQREPathTracer::EquationSystem::GetValue(const Vector &p_point, - Vector &p_lhs) const +void EquationSystem::GetValue(const Vector &p_point, Vector &p_lhs) const { - LogBehavProfile profile((BehaviorSupportProfile(m_game))); - for (int i = 1; i <= profile.Length(); i++) { - profile.SetLogProb(i, p_point[i]); - } - - double lambda = p_point[p_point.Length()]; - + LogBehavProfile profile(PointToLogProfile(m_game, p_point)); + double lambda = p_point.back(); for (int i = 1; i <= p_lhs.Length(); i++) { p_lhs[i] = m_equations[i]->Value(profile, lambda); } } -void AgentQREPathTracer::EquationSystem::GetJacobian(const Vector &p_point, - Matrix &p_matrix) const +void EquationSystem::GetJacobian(const Vector &p_point, Matrix &p_matrix) const { - LogBehavProfile profile((BehaviorSupportProfile(m_game))); - for (int i = 1; i <= profile.Length(); i++) { - profile.SetLogProb(i, p_point[i]); - } - double lambda = p_point[p_point.Length()]; + LogBehavProfile profile(PointToLogProfile(m_game, p_point)); + double lambda = p_point.back(); for (int i = 1; i <= m_equations.Length(); i++) { Vector column(p_point.Length()); @@ -239,292 +275,168 @@ void AgentQREPathTracer::EquationSystem::GetJacobian(const Vector &p_poi } } -class AgentQREPathTracer::CallbackFunction : public PathTracer::CallbackFunction { +class TracingCallbackFunction { public: - CallbackFunction(std::ostream &p_stream, const Game &p_game, bool p_fullGraph, int p_decimals) - : m_stream(p_stream), m_game(p_game), m_fullGraph(p_fullGraph), m_decimals(p_decimals) + TracingCallbackFunction(const Game &p_game, MixedBehaviorObserverFunctionType p_observer) + : m_game(p_game), m_observer(p_observer) { } + ~TracingCallbackFunction() = default; - ~CallbackFunction() override = default; - - void operator()(const Vector &p_point, bool p_isTerminal) const override; - + void AppendPoint(const Vector &p_point); const List &GetProfiles() const { return m_profiles; } private: - std::ostream &m_stream; Game m_game; - bool m_fullGraph; - int m_decimals; - mutable List m_profiles; + MixedBehaviorObserverFunctionType m_observer; + List m_profiles; }; -void AgentQREPathTracer::CallbackFunction::operator()(const Vector &p_point, - bool p_isTerminal) const +void TracingCallbackFunction::AppendPoint(const Vector &p_point) { - if ((!m_fullGraph || p_isTerminal) && (m_fullGraph || !p_isTerminal)) { - return; - } - - m_stream.setf(std::ios::fixed); - // By convention, we output lambda first - if (!p_isTerminal) { - m_stream << std::setprecision(m_decimals) << p_point.back(); - } - else { - m_stream << "NE"; - } - m_stream.unsetf(std::ios::fixed); - - for (int i = 1; i < p_point.Length(); i++) { - m_stream << "," << std::setprecision(m_decimals) << exp(p_point[i]); - } - - m_stream << std::endl; - - MixedBehaviorProfile profile(m_game); - for (int i = 1; i < p_point.Length(); i++) { - profile[i] = exp(p_point[i]); - } - m_profiles.push_back(LogitQREMixedBehaviorProfile(profile, p_point.back(), 0.0)); + MixedBehaviorProfile profile(PointToProfile(m_game, p_point)); + m_profiles.push_back(LogitQREMixedBehaviorProfile(profile, p_point.back(), 1.0)); + m_observer(m_profiles.back()); } -//------------------------------------------------------------------------------ -// AgentQREPathTracer: Criterion function -//------------------------------------------------------------------------------ - -class AgentQREPathTracer::LambdaCriterion : public PathTracer::CriterionFunction { +class EstimatorCallbackFunction { public: - explicit LambdaCriterion(double p_lambda) : m_lambda(p_lambda) {} + EstimatorCallbackFunction(const Game &p_game, const Vector &p_frequencies, + MixedBehaviorObserverFunctionType p_observer); + ~EstimatorCallbackFunction() = default; - double operator()(const Vector &p_point, const Vector &p_tangent) const override - { - return p_point.back() - m_lambda; - } + void EvaluatePoint(const Vector &p_point); + const LogitQREMixedBehaviorProfile &GetMaximizer() const { return m_bestProfile; } private: - double m_lambda; + Game m_game; + const Vector &m_frequencies; + MixedBehaviorObserverFunctionType m_observer; + LogitQREMixedBehaviorProfile m_bestProfile; }; -//------------------------------------------------------------------------------ -// AgentQREPathTracer: Wrapper to the tracing engine -//------------------------------------------------------------------------------ - -namespace { +EstimatorCallbackFunction::EstimatorCallbackFunction(const Game &p_game, + const Vector &p_frequencies, + MixedBehaviorObserverFunctionType p_observer) + : m_game(p_game), m_frequencies(p_frequencies), m_observer(p_observer), + m_bestProfile(MixedBehaviorProfile(p_game), 0.0, + LogLike(p_frequencies, static_cast &>( + MixedBehaviorProfile(p_game)))) +{ +} -bool RegretTerminationFunction(const Game &p_game, const Vector &p_point, double p_regret) +void EstimatorCallbackFunction::EvaluatePoint(const Vector &p_point) { - if (p_point.back() < 0.0) { - return true; - } - MixedBehaviorProfile profile(p_game); - for (int i = 1; i < p_point.Length(); i++) { - profile[i] = exp(p_point[i]); + MixedBehaviorProfile profile(PointToProfile(m_game, p_point)); + auto qre = LogitQREMixedBehaviorProfile( + profile, p_point.back(), + LogLike(m_frequencies, static_cast &>(profile))); + m_observer(qre); + if (qre.GetLogLike() > m_bestProfile.GetLogLike()) { + m_bestProfile = qre; } - return profile.GetMaxRegret() < p_regret; } } // namespace -List -AgentQREPathTracer::TraceAgentPath(const LogitQREMixedBehaviorProfile &p_start, - std::ostream &p_stream, double p_regret, double p_omega) const +namespace Gambit { + +List LogitBehaviorSolve(const LogitQREMixedBehaviorProfile &p_start, + double p_regret, double p_omega, + double p_firstStep, double p_maxAccel, + MixedBehaviorObserverFunctionType p_observer) { + PathTracer tracer; + tracer.SetMaxDecel(p_maxAccel); + tracer.SetStepsize(p_firstStep); + double scale = p_start.GetGame()->GetMaxPayoff() - p_start.GetGame()->GetMinPayoff(); if (scale != 0.0) { p_regret *= scale; } - List ret; - Vector x(p_start.BehaviorProfileLength() + 1); - for (size_t i = 1; i <= p_start.BehaviorProfileLength(); i++) { - x[i] = log(p_start[i]); - } - x.back() = p_start.GetLambda(); - - CallbackFunction func(p_stream, p_start.GetGame(), m_fullGraph, m_decimals); - TracePath( - EquationSystem(p_start.GetGame()), x, p_omega, - [p_start, p_regret](const Vector &p_point) { - return RegretTerminationFunction(p_start.GetGame(), p_point, p_regret); + Game game = p_start.GetGame(); + Vector x(ProfileToPoint(p_start)); + TracingCallbackFunction callback(game, p_observer); + EquationSystem system(game); + tracer.TracePath( + [&system](const Vector &p_point, Vector &p_lhs) { + system.GetValue(p_point, p_lhs); }, - func); - if (!m_fullGraph && func.GetProfiles().back().GetProfile().GetMaxRegret() >= p_regret) { - return {}; - } - return func.GetProfiles(); -} - -LogitQREMixedBehaviorProfile -AgentQREPathTracer::SolveAtLambda(const LogitQREMixedBehaviorProfile &p_start, - std::ostream &p_stream, double p_targetLambda, - double p_omega) const -{ - Vector x(p_start.BehaviorProfileLength() + 1); - for (int i = 1; i <= p_start.BehaviorProfileLength(); i++) { - x[i] = log(p_start[i]); - } - x.back() = p_start.GetLambda(); - CallbackFunction func(p_stream, p_start.GetGame(), m_fullGraph, m_decimals); - TracePath(EquationSystem(p_start.GetGame()), x, p_omega, LambdaPositiveTerminationFunction, func, - LambdaCriterion(p_targetLambda)); - return func.GetProfiles().back(); -} - -//---------------------------------------------------------------------------- -// AgentQREEstimator: Criterion function -//---------------------------------------------------------------------------- - -namespace { -double LogLike(const Vector &p_frequencies, const Vector &p_point) -{ - double logL = 0.0; - for (int i = 1; i <= p_frequencies.Length(); i++) { - logL += p_frequencies[i] * log(p_point[i]); - } - return logL; -} - -} // end anonymous namespace - -class AgentQREEstimator::CriterionFunction : public PathTracer::CriterionFunction { -public: - explicit CriterionFunction(const Vector &p_frequencies) : m_frequencies(p_frequencies) {} - ~CriterionFunction() override = default; - - double operator()(const Vector &p_point, const Vector &p_tangent) const override - { - double diff_logL = 0.0; - for (int i = 1; i <= m_frequencies.Length(); i++) { - diff_logL += m_frequencies[i] * p_tangent[i]; - } - return diff_logL; - } - -private: - Vector m_frequencies; -}; - -//---------------------------------------------------------------------------- -// AgentQREEstimator: Callback function -//---------------------------------------------------------------------------- - -class AgentQREEstimator::CallbackFunction : public PathTracer::CallbackFunction { -public: - CallbackFunction(std::ostream &p_stream, const Game &p_game, const Vector &p_frequencies, - bool p_fullGraph, int p_decimals); - ~CallbackFunction() override = default; - - void operator()(const Vector &p_point, bool p_isTerminal) const override; - - LogitQREMixedBehaviorProfile GetMaximizer() const - { - return {m_bestProfile, m_bestLambda, m_maxlogL}; - } - void PrintMaximizer() const; - -private: - void PrintProfile(const MixedBehaviorProfile &, double) const; - - std::ostream &m_stream; - Game m_game; - const Vector &m_frequencies; - bool m_fullGraph; - int m_decimals; - mutable MixedBehaviorProfile m_bestProfile; - mutable double m_bestLambda{0.0}; - mutable double m_maxlogL; -}; - -AgentQREEstimator::CallbackFunction::CallbackFunction(std::ostream &p_stream, const Game &p_game, - const Vector &p_frequencies, - bool p_fullGraph, int p_decimals) - : m_stream(p_stream), m_game(p_game), m_frequencies(p_frequencies), m_fullGraph(p_fullGraph), - m_decimals(p_decimals), m_bestProfile(p_game), - m_maxlogL(LogLike(p_frequencies, static_cast &>(m_bestProfile))) -{ -} - -void AgentQREEstimator::CallbackFunction::PrintProfile( - const MixedBehaviorProfile &p_profile, double p_logL) const -{ - for (size_t i = 1; i <= p_profile.BehaviorProfileLength(); i++) { - m_stream << "," << std::setprecision(m_decimals) << p_profile[i]; - } - m_stream.setf(std::ios::fixed); - m_stream << "," << std::setprecision(m_decimals); - m_stream << p_logL; - m_stream.unsetf(std::ios::fixed); -} - -void AgentQREEstimator::CallbackFunction::PrintMaximizer() const -{ - m_stream.setf(std::ios::fixed); - // By convention, we output lambda first - m_stream << std::setprecision(m_decimals) << m_bestLambda; - m_stream.unsetf(std::ios::fixed); - PrintProfile(m_bestProfile, m_maxlogL); - m_stream << std::endl; + [&system](const Vector &p_point, Matrix &p_jac) { + system.GetJacobian(p_point, p_jac); + }, + x, p_omega, + [game, p_regret](const Vector &p_point) { + return RegretTerminationFunction(game, p_point, p_regret); + }, + [&callback](const Vector &p_point) -> void { callback.AppendPoint(p_point); }); + return callback.GetProfiles(); } -void AgentQREEstimator::CallbackFunction::operator()(const Vector &x, - bool p_isTerminal) const +LogitQREMixedBehaviorProfile LogitBehaviorSolveLambda(const LogitQREMixedBehaviorProfile &p_start, + double p_targetLambda, double p_omega, + double p_firstStep, double p_maxAccel, + MixedBehaviorObserverFunctionType p_observer) { - m_stream.setf(std::ios::fixed); - // By convention, we output lambda first - if (!p_isTerminal) { - m_stream << std::setprecision(m_decimals) << x[x.Length()]; - } - else { - m_stream << "NE"; - } - m_stream.unsetf(std::ios::fixed); - MixedBehaviorProfile profile(m_game); - for (int i = 1; i < x.Length(); i++) { - profile[i] = exp(x[i]); - } - double logL = LogLike(m_frequencies, static_cast &>(profile)); - PrintProfile(profile, logL); - m_stream << std::endl; - if (logL > m_maxlogL) { - m_maxlogL = logL; - m_bestLambda = x[x.Length()]; - m_bestProfile = profile; - } + PathTracer tracer; + tracer.SetMaxDecel(p_maxAccel); + tracer.SetStepsize(p_firstStep); + + Game game = p_start.GetGame(); + Vector x(ProfileToPoint(p_start)); + TracingCallbackFunction callback(game, p_observer); + EquationSystem system(game); + tracer.TracePath( + [&system](const Vector &p_point, Vector &p_lhs) { + system.GetValue(p_point, p_lhs); + }, + [&system](const Vector &p_point, Matrix &p_jac) { + system.GetJacobian(p_point, p_jac); + }, + x, p_omega, LambdaPositiveTerminationFunction, + [&callback](const Vector &p_point) -> void { callback.AppendPoint(p_point); }, + [p_targetLambda](const Vector &x, const Vector &) -> double { + return x.back() - p_targetLambda; + }); + return callback.GetProfiles().back(); } -//---------------------------------------------------------------------------- -// AgentQREEstimator: Main driver routine -//---------------------------------------------------------------------------- - LogitQREMixedBehaviorProfile -AgentQREEstimator::Estimate(const LogitQREMixedBehaviorProfile &p_start, - const MixedBehaviorProfile &p_frequencies, - std::ostream &p_stream, double p_maxLambda, double p_omega) +LogitBehaviorEstimate(const MixedBehaviorProfile &p_frequencies, double p_maxLambda, + double p_omega, double p_stopAtLocal, double p_firstStep, double p_maxAccel, + MixedBehaviorObserverFunctionType p_observer) { - if (p_start.GetGame() != p_frequencies.GetGame()) { - throw MismatchException(); - } - - Vector x(p_start.BehaviorProfileLength() + 1); - for (int i = 1; i <= p_start.BehaviorProfileLength(); i++) { - x[i] = log(p_start[i]); - } - x.back() = p_start.GetLambda(); - - CallbackFunction callback(p_stream, p_start.GetGame(), - static_cast &>(p_frequencies), m_fullGraph, - m_decimals); + LogitQREMixedBehaviorProfile start(p_frequencies.GetGame()); + PathTracer tracer; + tracer.SetMaxDecel(p_maxAccel); + tracer.SetStepsize(p_firstStep); + + Vector x(ProfileToPoint(start)); + Vector freq_vector(static_cast &>(p_frequencies)); + EstimatorCallbackFunction callback( + start.GetGame(), static_cast &>(p_frequencies), p_observer); + EquationSystem system(start.GetGame()); while (x.back() < p_maxLambda) { - TracePath( - EquationSystem(p_start.GetGame()), x, p_omega, + tracer.TracePath( + [&system](const Vector &p_point, Vector &p_lhs) { + system.GetValue(p_point, p_lhs); + }, + [&system](const Vector &p_point, Matrix &p_jac) { + system.GetJacobian(p_point, p_jac); + }, + x, p_omega, [p_maxLambda](const Vector &p_point) { return LambdaRangeTerminationFunction(p_point, 0, p_maxLambda); }, - callback, CriterionFunction(static_cast &>(p_frequencies))); + [&callback](const Vector &p_point) -> void { callback.EvaluatePoint(p_point); }, + [freq_vector](const Vector &, const Vector &p_tangent) -> double { + return DiffLogLike(freq_vector, p_tangent); + }); + if (p_stopAtLocal) { + break; + } } - callback.PrintMaximizer(); return callback.GetMaximizer(); } diff --git a/src/solvers/logit/efglogit.h b/src/solvers/logit/efglogit.h deleted file mode 100644 index 67e567da7..000000000 --- a/src/solvers/logit/efglogit.h +++ /dev/null @@ -1,126 +0,0 @@ -// -// This file is part of Gambit -// Copyright (c) 1994-2024, The Gambit Project (http://www.gambit-project.org) -// -// FILE: src/tools/logit/efglogit.h -// Computation of agent quantal response equilibrium correspondence for -// extensive games. -// -// 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 EFGLOGIT_H -#define EFGLOGIT_H - -#include "path.h" - -namespace Gambit { - -class LogitQREMixedBehaviorProfile { -public: - explicit LogitQREMixedBehaviorProfile(const Game &p_game) : m_profile(p_game), m_lambda(0.0) {} - LogitQREMixedBehaviorProfile(const MixedBehaviorProfile &p_profile, double p_lambda, - double p_logLike) - : m_profile(p_profile), m_lambda(p_lambda), m_logLike(p_logLike) - { - } - double GetLambda() const { return m_lambda; } - const MixedBehaviorProfile &GetProfile() const { return m_profile; } - double GetLogLike() const { return m_logLike; } - - Game GetGame() const { return m_profile.GetGame(); } - size_t BehaviorProfileLength() const { return m_profile.BehaviorProfileLength(); } - double operator[](int i) const { return m_profile[i]; } - -private: - const MixedBehaviorProfile m_profile; - double m_lambda; - double m_logLike; -}; - -class AgentQREPathTracer : public PathTracer { -public: - AgentQREPathTracer() : m_fullGraph(true), m_decimals(6) {} - ~AgentQREPathTracer() override = default; - - List TraceAgentPath(const LogitQREMixedBehaviorProfile &p_start, - std::ostream &p_stream, double p_regret, - double p_omega) const; - LogitQREMixedBehaviorProfile SolveAtLambda(const LogitQREMixedBehaviorProfile &p_start, - std::ostream &p_logStream, double p_targetLambda, - double p_omega) const; - - void SetFullGraph(bool p_fullGraph) { m_fullGraph = p_fullGraph; } - bool GetFullGraph() const { return m_fullGraph; } - - void SetDecimals(int p_decimals) { m_decimals = p_decimals; } - int GetDecimals() const { return m_decimals; } - -protected: - bool m_fullGraph; - int m_decimals; - - class EquationSystem; - class CallbackFunction; - class LambdaCriterion; -}; - -class AgentQREEstimator : public AgentQREPathTracer { -public: - AgentQREEstimator() = default; - ~AgentQREEstimator() override = default; - - LogitQREMixedBehaviorProfile Estimate(const LogitQREMixedBehaviorProfile &p_start, - const MixedBehaviorProfile &p_frequencies, - std::ostream &p_logStream, double p_maxLambda, - double p_omega); - -protected: - class CriterionFunction; - class CallbackFunction; -}; - -inline LogitQREMixedBehaviorProfile -LogitBehaviorEstimate(const MixedBehaviorProfile &p_frequencies, double p_firstStep, - double p_maxAccel) -{ - LogitQREMixedBehaviorProfile start(p_frequencies.GetGame()); - AgentQREEstimator alg; - alg.SetMaxDecel(p_maxAccel); - alg.SetStepsize(p_firstStep); - std::ostringstream ostream; - return alg.Estimate(start, p_frequencies, ostream, 2.0, 1.0); -} - -inline List> LogitBehaviorSolve(const Game &p_game, double p_epsilon, - double p_firstStep, double p_maxAccel) -{ - AgentQREPathTracer tracer; - tracer.SetMaxDecel(p_maxAccel); - tracer.SetStepsize(p_firstStep); - tracer.SetFullGraph(false); - std::ostringstream ostream; - auto result = - tracer.TraceAgentPath(LogitQREMixedBehaviorProfile(p_game), ostream, p_epsilon, 1.0); - auto ret = List>(); - if (!result.empty()) { - ret.push_back(result.back().GetProfile()); - } - return ret; -} - -} // end namespace Gambit - -#endif // EFGLOGIT_H diff --git a/src/solvers/logit/logbehav.h b/src/solvers/logit/logbehav.h index 4f3f3952d..8da8cad55 100644 --- a/src/solvers/logit/logbehav.h +++ b/src/solvers/logit/logbehav.h @@ -49,34 +49,22 @@ using namespace Gambit; /// realization probabilities are going to zero, so as to be able to /// get a good approximation to the limiting sequential equilibrium. /// -template class LogBehavProfile : private DVector { +template class LogBehavProfile { protected: - BehaviorSupportProfile m_support; - DVector m_logProbs; + Game m_game; + DVector m_probs, m_logProbs; + // structures for storing cached data mutable bool m_cacheValid; + mutable std::map m_logRealizProbs; + mutable std::map m_beliefs; + mutable std::map> m_nodeValues; + mutable std::map m_actionValues; - // structures for storing cached data: nodes - mutable Vector m_realizProbs, m_logRealizProbs; - mutable Vector m_beliefs; - mutable Matrix m_nodeValues; - - // structures for storing cached data: information sets - mutable PVector m_infosetValues; - - // structures for storing cached data: actions - mutable DVector m_actionValues; // aka conditional payoffs - - const T &ActionValue(const GameAction &act) const - { - return m_actionValues(act->GetInfoset()->GetPlayer()->GetNumber(), - act->GetInfoset()->GetNumber(), act->GetNumber()); - } + const T &ActionValue(const GameAction &act) const { return m_actionValues[act]; } /// @name Auxiliary functions for computation of interesting values //@{ - void GetPayoff(GameTreeNodeRep *, const T &, int, T &) const; - void ComputeSolutionDataPass2(const GameNode &node) const; void ComputeSolutionDataPass1(const GameNode &node) const; void ComputeSolutionData() const; @@ -85,22 +73,10 @@ template class LogBehavProfile : private DVector { public: /// @name Lifecycle //@{ - explicit LogBehavProfile(const BehaviorSupportProfile &); - ~LogBehavProfile() override = default; + explicit LogBehavProfile(const Game &); + ~LogBehavProfile() = default; LogBehavProfile &operator=(const LogBehavProfile &) = delete; - LogBehavProfile &operator=(const Vector &p) - { - Invalidate(); - Vector::operator=(p); - return *this; - } - LogBehavProfile &operator=(const T &x) - { - Invalidate(); - DVector::operator=(x); - return *this; - } //@} @@ -109,19 +85,31 @@ template class LogBehavProfile : private DVector { bool operator==(const LogBehavProfile &) const; bool operator!=(const LogBehavProfile &x) const { return !(*this == x); } + void SetProb(const GameAction &p_action, const T &p_value) + { + m_probs(p_action->GetInfoset()->GetPlayer()->GetNumber(), p_action->GetInfoset()->GetNumber(), + p_action->GetNumber()) = p_value; + m_logProbs(p_action->GetInfoset()->GetPlayer()->GetNumber(), + p_action->GetInfoset()->GetNumber(), p_action->GetNumber()) = log(p_value); + } const T &GetProb(const GameAction &p_action) const { - return (*this)(p_action->GetInfoset()->GetPlayer()->GetNumber(), - p_action->GetInfoset()->GetNumber(), m_support.GetIndex(p_action)); + return m_probs(p_action->GetInfoset()->GetPlayer()->GetNumber(), + p_action->GetInfoset()->GetNumber(), p_action->GetNumber()); } const T &GetLogProb(int a, int b, int c) const { return m_logProbs(a, b, c); } - const T &GetProb(int a, int b, int c) const { return DVector::operator()(a, b, c); } - + const T &GetProb(int a, int b, int c) const { return m_probs(a, b, c); } + void SetProb(int a, const T &p_value) + { + Invalidate(); + m_logProbs[a] = log(p_value); + m_probs[a] = p_value; + } void SetLogProb(int a, const T &p_value) { Invalidate(); m_logProbs[a] = p_value; - Array::operator[](a) = exp(p_value); + m_probs[a] = exp(p_value); } //@} @@ -129,26 +117,15 @@ template class LogBehavProfile : private DVector { //@{ /// Force recomputation of stored quantities void Invalidate() const { m_cacheValid = false; } - /// Set the profile to the centroid - void SetCentroid(); //@} /// @name General data access //@{ - int Length() const { return Array::Length(); } - Game GetGame() const { return m_support.GetGame(); } - const BehaviorSupportProfile &GetSupport() const { return m_support; } + size_t BehaviorProfileLength() const { return m_probs.Length(); } //@} /// @name Computation of interesting quantities //@{ - T GetPayoff(int p_player) const; - - const T &GetRealizProb(const GameNode &node) const; - const T &GetBeliefProb(const GameNode &node) const; - Vector GetPayoff(const GameNode &node) const; - T GetInfosetProb(const GameInfoset &iset) const; - const T &GetPayoff(const GameInfoset &iset) const; T GetActionProb(const GameAction &act) const; T GetLogActionProb(const GameAction &) const; const T &GetPayoff(const GameAction &act) const; diff --git a/src/solvers/logit/logbehav.imp b/src/solvers/logit/logbehav.imp index b237c8252..4126523b0 100644 --- a/src/solvers/logit/logbehav.imp +++ b/src/solvers/logit/logbehav.imp @@ -29,15 +29,18 @@ //======================================================================== template -LogBehavProfile::LogBehavProfile(const BehaviorSupportProfile &p_support) - : DVector(p_support.NumActions()), m_support(p_support), m_logProbs(p_support.NumActions()), - m_cacheValid(false), m_realizProbs(p_support.GetGame()->NumNodes()), - m_logRealizProbs(p_support.GetGame()->NumNodes()), m_beliefs(p_support.GetGame()->NumNodes()), - m_nodeValues(p_support.GetGame()->NumNodes(), p_support.GetGame()->NumPlayers()), - m_infosetValues(p_support.GetGame()->NumInfosets()), - m_actionValues(p_support.GetGame()->NumActions()) +LogBehavProfile::LogBehavProfile(const Game &p_game) + : m_game(p_game), m_probs(m_game->NumActions()), m_logProbs(m_game->NumActions()), + m_cacheValid(false) { - SetCentroid(); + for (auto infoset : m_game->GetInfosets()) { + if (infoset->NumActions() > 0) { + T center = (T(1) / T(infoset->NumActions())); + for (auto act : infoset->GetActions()) { + SetProb(act, center); + } + } + } } //======================================================================== @@ -46,67 +49,13 @@ LogBehavProfile::LogBehavProfile(const BehaviorSupportProfile &p_support) template bool LogBehavProfile::operator==(const LogBehavProfile &p_profile) const { - return (m_support == p_profile.m_support && (DVector &)*this == (DVector &)p_profile); -} - -//======================================================================== -// LogBehavProfile: General data access -//======================================================================== - -template void LogBehavProfile::SetCentroid() -{ - for (int pl = 1; pl <= this->dvlen.Length(); pl++) { - for (int iset = 1; iset <= this->dvlen[pl]; iset++) { - if (m_support.NumActions(pl, iset) > 0) { - T center = (T)1 / (T)m_support.NumActions(pl, iset); - for (int act = 1; act <= this->svlen[this->dvidx[pl] + iset - 1]; act++) { - this->dvptr[pl][iset][act] = center; - m_logProbs(pl, iset, act) = log(center); - } - } - } - } - m_cacheValid = false; + return (m_game == p_profile.m_game && m_probs == p_profile.m_probs); } //======================================================================== // LogBehavProfile: Interesting quantities //======================================================================== -template const T &LogBehavProfile::GetRealizProb(const GameNode &node) const -{ - ComputeSolutionData(); - return m_realizProbs[node->GetNumber()]; -} - -template const T &LogBehavProfile::GetBeliefProb(const GameNode &node) const -{ - ComputeSolutionData(); - return m_beliefs[node->GetNumber()]; -} - -template Vector LogBehavProfile::GetPayoff(const GameNode &node) const -{ - ComputeSolutionData(); - return m_nodeValues.Row(node->GetNumber()); -} - -template T LogBehavProfile::GetInfosetProb(const GameInfoset &iset) const -{ - ComputeSolutionData(); - T prob = (T)0; - for (int i = 1; i <= iset->NumMembers(); i++) { - prob += m_realizProbs[iset->GetMember(i)->GetNumber()]; - } - return prob; -} - -template const T &LogBehavProfile::GetPayoff(const GameInfoset &iset) const -{ - ComputeSolutionData(); - return m_infosetValues(iset->GetPlayer()->GetNumber(), iset->GetNumber()); -} - template T LogBehavProfile::GetActionProb(const GameAction &action) const { if (action->GetInfoset()->GetPlayer()->IsChance()) { @@ -114,12 +63,9 @@ template T LogBehavProfile::GetActionProb(const GameAction &action) dynamic_cast(action->GetInfoset().operator->()); return static_cast(infoset->GetActionProb(action->GetNumber())); } - else if (!m_support.Contains(action)) { - return (T)0.0; - } else { - return (*this)(action->GetInfoset()->GetPlayer()->GetNumber(), - action->GetInfoset()->GetNumber(), m_support.GetIndex(action)); + return GetProb(action->GetInfoset()->GetPlayer()->GetNumber(), + action->GetInfoset()->GetNumber(), action->GetNumber()); } } @@ -132,50 +78,14 @@ template T LogBehavProfile::GetLogActionProb(const GameAction &acti } else { return m_logProbs(action->GetInfoset()->GetPlayer()->GetNumber(), - action->GetInfoset()->GetNumber(), m_support.GetIndex(action)); + action->GetInfoset()->GetNumber(), action->GetNumber()); } } template const T &LogBehavProfile::GetPayoff(const GameAction &act) const { ComputeSolutionData(); - return m_actionValues(act->GetInfoset()->GetPlayer()->GetNumber(), - act->GetInfoset()->GetNumber(), act->GetNumber()); -} - -template -void LogBehavProfile::GetPayoff(GameTreeNodeRep *node, const T &prob, int player, - T &value) const -{ - if (node->GetOutcome()) { - value += prob * static_cast(node->GetOutcome()->GetPayoff(player)); - } - - if (node->NumChildren()) { - int pl = node->GetInfoset()->GetPlayer()->GetNumber(); - int iset = node->GetInfoset()->GetNumber(); - if (pl == 0) { - // chance player - for (int act = 1; act <= node->NumChildren(); act++) { - GetPayoff(node->GetChild(act), - prob * node->GetInfoset()->GetActionProb(act, static_cast(0)), player, value); - } - } - else { - for (int act = 1; act <= m_support.NumActions(pl, iset); act++) { - GameActionRep *action = m_support.GetAction(pl, iset, act); - GetPayoff(node->GetChild(action->GetNumber()), prob * GetActionProb(action), player, - value); - } - } - } -} - -template T LogBehavProfile::GetPayoff(int player) const -{ - T value = (T)0; - GetPayoff(m_support.GetGame()->GetRoot(), (T)1, player, value); - return value; + return m_actionValues[act]; } // @@ -205,32 +115,24 @@ T LogBehavProfile::DiffActionValue(const GameAction &p_action, { ComputeSolutionData(); - T deriv = (T)0; GameInfoset infoset = p_action->GetInfoset(); GamePlayer player = p_action->GetInfoset()->GetPlayer(); // derivs stores the ratio of the derivative of the realization probability // for each node, divided by the realization probability of the infoset, // times the probability with which p_oppAction is played - Array derivs(infoset->NumMembers()); - - for (int i = 1; i <= infoset->NumMembers(); i++) { - derivs[i] = 0.0; - GameAction act = GetPrecedingAction(infoset->GetMember(i), p_oppAction->GetInfoset()); - - if (act == p_oppAction) { - derivs[i] = m_beliefs[infoset->GetMember(i)->GetNumber()]; - } + std::map derivs; + for (auto member : infoset->GetMembers()) { + GameAction act = GetPrecedingAction(member, p_oppAction->GetInfoset()); + derivs[member] = (act == p_oppAction) ? m_beliefs[member] : T(0); } - for (int i = 1; i <= infoset->NumMembers(); i++) { - GameNode member = infoset->GetMember(i); - GameNode child = member->GetChild(p_action->GetNumber()); - - deriv += derivs[i] * m_nodeValues(child->GetNumber(), player->GetNumber()); - deriv -= derivs[i] * GetPayoff(p_action); - deriv += GetProb(p_oppAction) * m_beliefs[member->GetNumber()] * - DiffNodeValue(child, player, p_oppAction); + T deriv = T(0); + for (auto member : infoset->GetMembers()) { + GameNode child = member->GetChild(p_action); + deriv += derivs[member] * m_nodeValues[child][player]; + deriv -= derivs[member] * GetPayoff(p_action); + deriv += GetProb(p_oppAction) * m_beliefs[member] * DiffNodeValue(child, player, p_oppAction); } return deriv; @@ -253,14 +155,13 @@ T LogBehavProfile::DiffNodeValue(const GameNode &p_node, const GamePlayer &p_ // 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 m_nodeValues(p_node->GetChild(p_oppAction->GetNumber())->GetNumber(), - p_player->GetNumber()); + return m_nodeValues[p_node->GetChild(p_oppAction)][p_player]; } else { - T deriv = (T)0; - for (int act = 1; act <= infoset->NumActions(); act++) { - deriv += (DiffNodeValue(p_node->GetChild(act), p_player, p_oppAction) * - GetActionProb(infoset->GetAction(act))); + T deriv = T(0); + for (auto action : infoset->GetActions()) { + deriv += + (DiffNodeValue(p_node->GetChild(action), p_player, p_oppAction) * GetActionProb(action)); } return deriv; } @@ -274,67 +175,44 @@ template void LogBehavProfile::ComputeSolutionDataPass2(const GameN { if (node->GetOutcome()) { GameOutcome outcome = node->GetOutcome(); - for (int pl = 1; pl <= m_support.GetGame()->NumPlayers(); pl++) { - m_nodeValues(node->GetNumber(), pl) += static_cast(outcome->GetPayoff(pl)); + for (auto player : m_game->GetPlayers()) { + m_nodeValues[node][player] += static_cast(outcome->GetPayoff(player)); } } - GameInfoset iset = node->GetInfoset(); + GameInfoset infoset = node->GetInfoset(); + if (!infoset) { + return; + } - if (iset) { - T infosetProb = (T)0; - for (int i = 1; i <= iset->NumMembers(); i++) { - infosetProb += m_realizProbs[iset->GetMember(i)->GetNumber()]; - } + // push down payoffs from outcomes attached to non-terminal nodes + for (auto child : node->GetChildren()) { + m_nodeValues[child] = m_nodeValues[node]; + } - // push down payoffs from outcomes attached to non-terminal nodes - for (int child = 1; child <= node->NumChildren(); child++) { - m_nodeValues.SetRow(node->GetChild(child)->GetNumber(), m_nodeValues.Row(node->GetNumber())); - } + for (auto player : m_game->GetPlayers()) { + m_nodeValues[node][player] = T(0); + } - for (int pl = 1; pl <= m_support.GetGame()->NumPlayers(); pl++) { - m_nodeValues(node->GetNumber(), pl) = (T)0; + for (auto child : node->GetChildren()) { + ComputeSolutionDataPass2(child); + GameAction action = child->GetPriorAction(); + for (auto player : m_game->GetPlayers()) { + m_nodeValues[node][player] += GetActionProb(action) * m_nodeValues[child][player]; } - - for (int child = 1; child <= node->NumChildren(); child++) { - GameNode childNode = node->GetChild(child); - ComputeSolutionDataPass2(childNode); - - GameAction act = childNode->GetPriorAction(); - - for (int pl = 1; pl <= m_support.GetGame()->NumPlayers(); pl++) { - m_nodeValues(node->GetNumber(), pl) += - GetActionProb(act) * m_nodeValues(childNode->GetNumber(), pl); - } - - if (!iset->IsChanceInfoset()) { - T &cpay = m_actionValues(act->GetInfoset()->GetPlayer()->GetNumber(), - act->GetInfoset()->GetNumber(), act->GetNumber()); - cpay += m_beliefs[node->GetNumber()] * - m_nodeValues(childNode->GetNumber(), iset->GetPlayer()->GetNumber()); - } + if (!infoset->IsChanceInfoset()) { + m_actionValues[action] += m_beliefs[node] * m_nodeValues[child][infoset->GetPlayer()]; } } } -// compute realization probabilities for nodes and isets. template void LogBehavProfile::ComputeSolutionDataPass1(const GameNode &node) const { - if (node->GetParent()) { - m_realizProbs[node->GetNumber()] = - m_realizProbs[node->GetParent()->GetNumber()] * GetActionProb(node->GetPriorAction()); - m_logRealizProbs[node->GetNumber()] = m_logRealizProbs[node->GetParent()->GetNumber()] + - GetLogActionProb(node->GetPriorAction()); - } - else { - m_realizProbs[node->GetNumber()] = (T)1; - m_logRealizProbs[node->GetNumber()] = (T)0.0; - } - - if (node->GetInfoset()) { - for (int i = 1; i <= node->NumChildren(); i++) { - ComputeSolutionDataPass1(node->GetChild(i)); - } + m_logRealizProbs[node] = (node->GetParent()) ? m_logRealizProbs[node->GetParent()] + + GetLogActionProb(node->GetPriorAction()) + : T(0); + for (auto child : node->GetChildren()) { + ComputeSolutionDataPass1(child); } } @@ -343,69 +221,48 @@ template void LogBehavProfile::ComputeSolutionData() const if (m_cacheValid) { return; } - m_actionValues = (T)0; - m_nodeValues = (T)0; - m_infosetValues = (T)0; - ComputeSolutionDataPass1(m_support.GetGame()->GetRoot()); - - for (int pl = 1; pl <= m_support.GetGame()->NumPlayers(); pl++) { - GamePlayer player = m_support.GetGame()->GetPlayer(pl); - for (int iset = 1; iset <= player->NumInfosets(); iset++) { - GameInfoset infoset = player->GetInfoset(iset); + m_actionValues.clear(); + m_beliefs.clear(); + m_nodeValues.clear(); + ComputeSolutionDataPass1(m_game->GetRoot()); + for (auto player : m_game->GetPlayers()) { + for (auto infoset : player->GetInfosets()) { // The log-profile assumes that the mixed behavior profile has full support. // However, if a game has zero-probability chance actions, then it is possible // for an information set not to be reached. In this event, we set the beliefs // at those information sets to be uniform across the nodes. - T infosetProb = (T)0.0; - for (int i = 1; i <= infoset->NumMembers(); i++) { - infosetProb += m_realizProbs[infoset->GetMember(i)->GetNumber()]; + T infosetProb = T(0); + for (auto member : infoset->GetMembers()) { + infosetProb += exp(m_logRealizProbs[member]); } - if (infosetProb == (T)0.0) { - for (int i = 1; i <= infoset->NumMembers(); i++) { - m_beliefs[infoset->GetMember(i)->GetNumber()] = 1.0 / (T)infoset->NumMembers(); + if (infosetProb == T(0)) { + for (auto member : infoset->GetMembers()) { + m_beliefs[member] = 1.0 / T(infoset->NumMembers()); } continue; } - T maxLogProb = m_logRealizProbs[infoset->GetMember(1)->GetNumber()]; - for (int i = 2; i <= infoset->NumMembers(); i++) { - if (m_logRealizProbs[infoset->GetMember(i)->GetNumber()] > maxLogProb) { - maxLogProb = m_logRealizProbs[infoset->GetMember(i)->GetNumber()]; + T maxLogProb = m_logRealizProbs[infoset->GetMember(1)]; + for (auto member : infoset->GetMembers()) { + if (m_logRealizProbs[member] > maxLogProb) { + maxLogProb = m_logRealizProbs[member]; } } T total = 0.0; - for (int i = 1; i <= infoset->NumMembers(); i++) { - total += exp(m_logRealizProbs[infoset->GetMember(i)->GetNumber()] - maxLogProb); + for (auto member : infoset->GetMembers()) { + total += exp(m_logRealizProbs[member] - maxLogProb); } // The belief for the most likely node T mostLikelyBelief = 1.0 / total; - for (int i = 1; i <= infoset->NumMembers(); i++) { - m_beliefs[infoset->GetMember(i)->GetNumber()] = - mostLikelyBelief * - exp(m_logRealizProbs[infoset->GetMember(i)->GetNumber()] - maxLogProb); + for (auto member : infoset->GetMembers()) { + m_beliefs[member] = mostLikelyBelief * exp(m_logRealizProbs[member] - maxLogProb); } } } - ComputeSolutionDataPass2(m_support.GetGame()->GetRoot()); - - // At this point, mark the cache as value, so calls to GetPayoff() - // don't create a loop. + ComputeSolutionDataPass2(m_game->GetRoot()); m_cacheValid = true; - - for (int pl = 1; pl <= m_support.GetGame()->NumPlayers(); pl++) { - for (int iset = 1; iset <= m_support.GetGame()->NumInfosets()[pl]; iset++) { - GameInfoset infoset = m_support.GetGame()->GetPlayer(pl)->GetInfoset(iset); - - m_infosetValues(infoset->GetPlayer()->GetNumber(), infoset->GetNumber()) = (T)0; - for (int act = 1; act <= infoset->NumActions(); act++) { - GameAction action = infoset->GetAction(act); - m_infosetValues(infoset->GetPlayer()->GetNumber(), infoset->GetNumber()) += - GetActionProb(action) * ActionValue(action); - } - } - } } diff --git a/src/solvers/logit/logit.h b/src/solvers/logit/logit.h new file mode 100644 index 000000000..ddf575275 --- /dev/null +++ b/src/solvers/logit/logit.h @@ -0,0 +1,126 @@ +// +// This file is part of Gambit +// Copyright (c) 1994-2024, The Gambit Project (http://www.gambit-project.org) +// +// FILE: src/solvers/logit/logit.h +// Computation of quantal response equilibrium correspondence +// +// 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 SOLVERS_LOGIT_H +#define SOLVERS_LOGIT_H + +#include + +namespace Gambit { + +template class LogitQRE { +public: + explicit LogitQRE(const Game &p_game); + LogitQRE(const T &p_profile, double p_lambda, double p_logLike = 1.0) + : m_profile(p_profile), m_lambda(p_lambda), m_logLike(p_logLike) + { + } + LogitQRE(const LogitQRE &p_qre) = default; + ~LogitQRE() = default; + + double GetLambda() const { return m_lambda; } + + const T &GetProfile() const { return m_profile; } + + double GetLogLike() const { return m_logLike; } + + Game GetGame() const { return m_profile.GetGame(); } + + size_t size() const; + + double operator[](int i) const { return m_profile[i]; } + +private: + T m_profile; + double m_lambda; + double m_logLike; +}; + +template <> +inline LogitQRE>::LogitQRE(const Game &p_game) + : m_profile(p_game->NewMixedStrategyProfile(0.0)), m_lambda(0.0), m_logLike(1.0) +{ +} + +template <> inline size_t LogitQRE>::size() const +{ + return m_profile.MixedProfileLength(); +} + +template <> +inline LogitQRE>::LogitQRE(const Game &p_game) + : m_profile(MixedBehaviorProfile(p_game)), m_lambda(0.0), m_logLike(1.0) +{ +} + +template <> inline size_t LogitQRE>::size() const +{ + return m_profile.BehaviorProfileLength(); +} + +using LogitQREMixedStrategyProfile = LogitQRE>; + +using MixedStrategyObserverFunctionType = + std::function; + +inline void NullMixedStrategyObserver(const LogitQREMixedStrategyProfile &) {} + +List +LogitStrategySolve(const LogitQREMixedStrategyProfile &p_start, double p_regret, double p_omega, + double p_firstStep, double p_maxAccel, + MixedStrategyObserverFunctionType p_observer = NullMixedStrategyObserver); + +LogitQREMixedStrategyProfile +LogitStrategySolveLambda(const LogitQREMixedStrategyProfile &p_start, double p_targetLambda, + double p_omega, double p_firstStep, double p_maxAccel, + MixedStrategyObserverFunctionType p_observer = NullMixedStrategyObserver); + +LogitQREMixedStrategyProfile +LogitStrategyEstimate(const MixedStrategyProfile &p_frequencies, double p_maxLambda, + double p_omega, double p_stopAtLocal, double p_firstStep, double p_maxAccel, + MixedStrategyObserverFunctionType p_observer = NullMixedStrategyObserver); + +using LogitQREMixedBehaviorProfile = LogitQRE>; + +using MixedBehaviorObserverFunctionType = + std::function; + +inline void NullMixedBehaviorObserver(const LogitQREMixedBehaviorProfile &) {} + +List +LogitBehaviorSolve(const LogitQREMixedBehaviorProfile &p_start, double p_regret, double p_omega, + double p_firstStep, double p_maxAccel, + MixedBehaviorObserverFunctionType p_observer = NullMixedBehaviorObserver); + +LogitQREMixedBehaviorProfile +LogitBehaviorSolveLambda(const LogitQREMixedBehaviorProfile &p_start, double p_targetLambda, + double p_omega, double p_firstStep, double p_maxAccel, + MixedBehaviorObserverFunctionType p_observer = NullMixedBehaviorObserver); + +LogitQREMixedBehaviorProfile +LogitBehaviorEstimate(const MixedBehaviorProfile &p_frequencies, double p_maxLambda, + double p_omega, double p_stopAtLocal, double p_firstStep, double p_maxAccel, + MixedBehaviorObserverFunctionType p_observer = NullMixedBehaviorObserver); + +} // namespace Gambit + +#endif // SOLVERS_LOGIT_H diff --git a/src/solvers/logit/nfglogit.cc b/src/solvers/logit/nfglogit.cc index a47168073..99aea0814 100644 --- a/src/solvers/logit/nfglogit.cc +++ b/src/solvers/logit/nfglogit.cc @@ -22,44 +22,71 @@ // #include -#include -#include #include "gambit.h" -#include "nfglogit.h" +#include "logit.h" +#include "path.h" namespace Gambit { -//------------------------------------------------------------------------------ -// StrategicQREPathTracer: Classes representing equations -//------------------------------------------------------------------------------ +namespace { -class StrategicQREPathTracer::EquationSystem : public PathTracer::EquationSystem { -public: - explicit EquationSystem(const Game &p_game) : m_game(p_game) {} - ~EquationSystem() override = default; - // Compute the value of the system of equations at the specified point. - void GetValue(const Vector &p_point, Vector &p_lhs) const override; - // Compute the Jacobian matrix at the specified point. - void GetJacobian(const Vector &p_point, Matrix &p_matrix) const override; +MixedStrategyProfile PointToProfile(const Game &p_game, const Vector &p_point) +{ + MixedStrategyProfile profile(p_game->NewMixedStrategyProfile(0.0)); + for (int i = 1; i < p_point.Length(); i++) { + profile[i] = exp(p_point[i]); + } + return profile; +} -private: - Game m_game; -}; +Vector ProfileToPoint(const LogitQREMixedStrategyProfile &p_profile) +{ + Vector point(p_profile.size() + 1); + for (int i = 1; i <= p_profile.size(); i++) { + point[i] = log(p_profile[i]); + } + point.back() = p_profile.GetLambda(); + return point; +} + +double LogLike(const Vector &p_frequencies, const Vector &p_point) +{ + double logL = 0.0; + for (int i = 1; i <= p_frequencies.Length(); i++) { + logL += p_frequencies[i] * log(p_point[i]); + } + return logL; +} -void StrategicQREPathTracer::EquationSystem::GetValue(const Vector &p_point, - Vector &p_lhs) const +double DiffLogLike(const Vector &p_frequencies, const Vector &p_tangent) { - MixedStrategyProfile profile(m_game->NewMixedStrategyProfile(0.0)), - logprofile(m_game->NewMixedStrategyProfile(0.0)); + double diff_logL = 0.0; + for (int i = 1; i <= p_frequencies.Length(); i++) { + diff_logL += p_frequencies[i] * p_tangent[i]; + } + return diff_logL; +} + +bool RegretTerminationFunction(const Game &p_game, const Vector &p_point, double p_regret) +{ + if (p_point.back() < 0.0) { + return true; + } + return PointToProfile(p_game, p_point).GetMaxRegret() < p_regret; +} + +void GetValue(const Game &p_game, const Vector &p_point, Vector &p_lhs) +{ + MixedStrategyProfile profile(PointToProfile(p_game, p_point)), + logprofile(p_game->NewMixedStrategyProfile(0.0)); for (size_t i = 1; i <= profile.MixedProfileLength(); i++) { - profile[i] = exp(p_point[i]); logprofile[i] = p_point[i]; } - double lambda = p_point[p_point.Length()]; + double lambda = p_point.back(); p_lhs = 0.0; - for (int rowno = 0, pl = 1; pl <= m_game->NumPlayers(); pl++) { - GamePlayer player = m_game->GetPlayer(pl); + for (int rowno = 0, pl = 1; pl <= p_game->NumPlayers(); pl++) { + GamePlayer player = p_game->GetPlayer(pl); for (size_t st = 1; st <= player->GetStrategies().size(); st++) { rowno++; if (st == 1) { @@ -79,27 +106,25 @@ void StrategicQREPathTracer::EquationSystem::GetValue(const Vector &p_po } } -void StrategicQREPathTracer::EquationSystem::GetJacobian(const Vector &p_point, - Matrix &p_matrix) const +void GetJacobian(const Game &p_game, const Vector &p_point, Matrix &p_matrix) { - MixedStrategyProfile profile(m_game->NewMixedStrategyProfile(0.0)), - logprofile(m_game->NewMixedStrategyProfile(0.0)); + MixedStrategyProfile profile(PointToProfile(p_game, p_point)), + logprofile(p_game->NewMixedStrategyProfile(0.0)); for (size_t i = 1; i <= profile.MixedProfileLength(); i++) { - profile[i] = exp(p_point[i]); logprofile[i] = p_point[i]; } - double lambda = p_point[p_point.Length()]; + double lambda = p_point.back(); p_matrix = 0.0; - for (int rowno = 0, i = 1; i <= m_game->NumPlayers(); i++) { - GamePlayer player = m_game->GetPlayer(i); + for (int rowno = 0, i = 1; i <= p_game->NumPlayers(); i++) { + GamePlayer player = p_game->GetPlayer(i); for (size_t j = 1; j <= player->GetStrategies().size(); j++) { rowno++; if (j == 1) { // This is a sum-to-one equation - for (int colno = 0, ell = 1; ell <= m_game->NumPlayers(); ell++) { - GamePlayer player2 = m_game->GetPlayer(ell); + for (int colno = 0, ell = 1; ell <= p_game->NumPlayers(); ell++) { + GamePlayer player2 = p_game->GetPlayer(ell); for (size_t m = 1; m <= player2->GetStrategies().size(); m++) { colno++; if (i == ell) { @@ -108,12 +133,12 @@ void StrategicQREPathTracer::EquationSystem::GetJacobian(const Vector &p // Otherwise, entry is zero } } - // The last column is derivative wrt lamba, which is zero + // The last column is derivative wrt lambda, which is zero } else { // This is a ratio equation - for (int colno = 0, ell = 1; ell <= m_game->NumPlayers(); ell++) { - GamePlayer player2 = m_game->GetPlayer(ell); + for (int colno = 0, ell = 1; ell <= p_game->NumPlayers(); ell++) { + GamePlayer player2 = p_game->GetPlayer(ell); for (size_t m = 1; m <= player2->GetStrategies().size(); m++) { colno++; if (i == ell) { @@ -141,288 +166,162 @@ void StrategicQREPathTracer::EquationSystem::GetJacobian(const Vector &p } } -//---------------------------------------------------------------------------- -// StrategicQREPathTracer: Criterion function -//---------------------------------------------------------------------------- - -class StrategicQREPathTracer::LambdaCriterion : public PathTracer::CriterionFunction { +class TracingCallbackFunction { public: - explicit LambdaCriterion(double p_lambda) : m_lambda(p_lambda) {} - - double operator()(const Vector &p_point, const Vector &p_tangent) const override + TracingCallbackFunction(const Game &p_game, MixedStrategyObserverFunctionType p_observer) + : m_game(p_game), m_observer(p_observer) { - return p_point[p_point.Length()] - m_lambda; } + ~TracingCallbackFunction() = default; + + void AppendPoint(const Vector &p_point); + const List &GetProfiles() const { return m_profiles; } private: - double m_lambda; + Game m_game; + MixedStrategyObserverFunctionType m_observer; + List m_profiles; }; -//---------------------------------------------------------------------------- -// StrategicQREPathTracer: Callback function -//---------------------------------------------------------------------------- +void TracingCallbackFunction::AppendPoint(const Vector &p_point) +{ + MixedStrategyProfile profile(PointToProfile(m_game, p_point)); + m_profiles.push_back(LogitQREMixedStrategyProfile(profile, p_point.back(), 1.0)); + m_observer(m_profiles.back()); +} -class StrategicQREPathTracer::CallbackFunction : public PathTracer::CallbackFunction { +class EstimatorCallbackFunction { public: - CallbackFunction(std::ostream &p_stream, const Game &p_game, bool p_fullGraph, int p_decimals) - : m_stream(p_stream), m_game(p_game), m_fullGraph(p_fullGraph), m_decimals(p_decimals) - { - } - ~CallbackFunction() override = default; + EstimatorCallbackFunction(const Game &p_game, const Vector &p_frequencies, + MixedStrategyObserverFunctionType p_observer); + ~EstimatorCallbackFunction() = default; - void operator()(const Vector &p_point, bool p_isTerminal) const override; - const List &GetProfiles() const { return m_profiles; } + void EvaluatePoint(const Vector &p_point); + + const LogitQREMixedStrategyProfile &GetMaximizer() const { return m_bestProfile; } private: - std::ostream &m_stream; Game m_game; - bool m_fullGraph; - int m_decimals; - mutable List m_profiles; + const Vector &m_frequencies; + MixedStrategyObserverFunctionType m_observer; + LogitQREMixedStrategyProfile m_bestProfile; }; -void StrategicQREPathTracer::CallbackFunction::operator()(const Vector &p_point, - bool p_isTerminal) const +EstimatorCallbackFunction::EstimatorCallbackFunction(const Game &p_game, + const Vector &p_frequencies, + MixedStrategyObserverFunctionType p_observer) + : m_game(p_game), m_frequencies(p_frequencies), m_observer(p_observer), + m_bestProfile(p_game->NewMixedStrategyProfile(0.0), 0.0, + LogLike(p_frequencies, static_cast &>( + p_game->NewMixedStrategyProfile(0.0)))) { - if ((!m_fullGraph || p_isTerminal) && (m_fullGraph || !p_isTerminal)) { - return; - } - m_stream.setf(std::ios::fixed); - // By convention, we output lambda first - if (!p_isTerminal) { - m_stream << std::setprecision(m_decimals) << p_point[p_point.Length()]; - } - else { - m_stream << "NE"; - } - m_stream.unsetf(std::ios::fixed); - MixedStrategyProfile profile(m_game->NewMixedStrategyProfile(0.0)); - for (int i = 1; i < p_point.Length(); i++) { - profile[i] = exp(p_point[i]); - m_stream << "," << std::setprecision(m_decimals) << profile[i]; - } - m_stream << std::endl; - m_profiles.push_back(LogitQREMixedStrategyProfile(profile, p_point.back(), 0.0)); } -//---------------------------------------------------------------------------- -// StrategicQREPathTracer: Main driver routines -//---------------------------------------------------------------------------- - -namespace { - -bool RegretTerminationFunction(const Game &p_game, const Vector &p_point, double p_regret) +void EstimatorCallbackFunction::EvaluatePoint(const Vector &p_point) { - if (p_point.back() < 0.0) { - return true; - } - MixedStrategyProfile profile(p_game->NewMixedStrategyProfile(0.0)); - for (int i = 1; i < p_point.Length(); i++) { - profile[i] = exp(p_point[i]); + MixedStrategyProfile profile(PointToProfile(m_game, p_point)); + auto qre = LogitQREMixedStrategyProfile( + profile, p_point.back(), + LogLike(m_frequencies, static_cast &>(profile))); + m_observer(qre); + if (qre.GetLogLike() > m_bestProfile.GetLogLike()) { + m_bestProfile = qre; } - return profile.GetMaxRegret() < p_regret; } } // namespace -List -StrategicQREPathTracer::TraceStrategicPath(const LogitQREMixedStrategyProfile &p_start, - std::ostream &p_stream, double p_regret, - double p_omega) const +List LogitStrategySolve(const LogitQREMixedStrategyProfile &p_start, + double p_regret, double p_omega, + double p_firstStep, double p_maxAccel, + MixedStrategyObserverFunctionType p_observer) { + PathTracer tracer; + tracer.SetMaxDecel(p_maxAccel); + tracer.SetStepsize(p_firstStep); + double scale = p_start.GetGame()->GetMaxPayoff() - p_start.GetGame()->GetMinPayoff(); if (scale != 0.0) { p_regret *= scale; } - Vector x(p_start.MixedProfileLength() + 1); - for (int i = 1; i <= p_start.MixedProfileLength(); i++) { - x[i] = log(p_start[i]); - } - x.back() = p_start.GetLambda(); - CallbackFunction func(p_stream, p_start.GetGame(), m_fullGraph, m_decimals); - TracePath( - EquationSystem(p_start.GetGame()), x, p_omega, + Vector x(ProfileToPoint(p_start)); + TracingCallbackFunction callback(p_start.GetGame(), p_observer); + tracer.TracePath( + [&p_start](const Vector &p_point, Vector &p_lhs) { + GetValue(p_start.GetGame(), p_point, p_lhs); + }, + [&p_start](const Vector &p_point, Matrix &p_jac) { + GetJacobian(p_start.GetGame(), p_point, p_jac); + }, + x, p_omega, [p_start, p_regret](const Vector &p_point) { return RegretTerminationFunction(p_start.GetGame(), p_point, p_regret); }, - func); - if (!m_fullGraph && func.GetProfiles().back().GetProfile().GetMaxRegret() >= p_regret) { - return {}; - } - return func.GetProfiles(); -} - -LogitQREMixedStrategyProfile -StrategicQREPathTracer::SolveAtLambda(const LogitQREMixedStrategyProfile &p_start, - std::ostream &p_stream, double p_targetLambda, - double p_omega) const -{ - Vector x(p_start.MixedProfileLength() + 1); - for (int i = 1; i <= p_start.MixedProfileLength(); i++) { - x[i] = log(p_start[i]); - } - x.back() = p_start.GetLambda(); - CallbackFunction func(p_stream, p_start.GetGame(), m_fullGraph, m_decimals); - TracePath(EquationSystem(p_start.GetGame()), x, p_omega, LambdaPositiveTerminationFunction, func, - LambdaCriterion(p_targetLambda)); - return func.GetProfiles().back(); -} - -//---------------------------------------------------------------------------- -// StrategicQREEstimator: Criterion function -//---------------------------------------------------------------------------- - -namespace { -double LogLike(const Vector &p_frequencies, const Vector &p_point) -{ - double logL = 0.0; - for (int i = 1; i <= p_frequencies.Length(); i++) { - logL += p_frequencies[i] * log(p_point[i]); - } - return logL; -} - -} // end anonymous namespace - -class StrategicQREEstimator::CriterionFunction : public PathTracer::CriterionFunction { -public: - explicit CriterionFunction(const Vector &p_frequencies) : m_frequencies(p_frequencies) {} - ~CriterionFunction() override = default; - - double operator()(const Vector &p_point, const Vector &p_tangent) const override - { - double diff_logL = 0.0; - for (int i = 1; i <= m_frequencies.Length(); i++) { - diff_logL += m_frequencies[i] * p_tangent[i]; - } - return diff_logL; - } - -private: - Vector m_frequencies; -}; - -//---------------------------------------------------------------------------- -// StrategicQREEstimator: Callback function -//---------------------------------------------------------------------------- - -class StrategicQREEstimator::CallbackFunction : public PathTracer::CallbackFunction { -public: - CallbackFunction(std::ostream &p_stream, const Game &p_game, const Vector &p_frequencies, - bool p_fullGraph, int p_decimals); - ~CallbackFunction() override = default; - - void operator()(const Vector &p_point, bool p_isTerminal) const override; - - LogitQREMixedStrategyProfile GetMaximizer() const - { - return {m_bestProfile, m_bestLambda, m_maxlogL}; - } - void PrintMaximizer() const; - -private: - void PrintProfile(const MixedStrategyProfile &, double) const; - - std::ostream &m_stream; - Game m_game; - const Vector &m_frequencies; - bool m_fullGraph; - int m_decimals; - mutable MixedStrategyProfile m_bestProfile; - mutable double m_bestLambda{0.0}; - mutable double m_maxlogL; -}; - -StrategicQREEstimator::CallbackFunction::CallbackFunction(std::ostream &p_stream, - const Game &p_game, - const Vector &p_frequencies, - bool p_fullGraph, int p_decimals) - : m_stream(p_stream), m_game(p_game), m_frequencies(p_frequencies), m_fullGraph(p_fullGraph), - m_decimals(p_decimals), m_bestProfile(p_game->NewMixedStrategyProfile(0.0)), - m_maxlogL(LogLike(p_frequencies, static_cast &>(m_bestProfile))) -{ -} - -void StrategicQREEstimator::CallbackFunction::PrintProfile( - const MixedStrategyProfile &p_profile, double p_logL) const -{ - for (size_t i = 1; i <= p_profile.MixedProfileLength(); i++) { - m_stream << "," << std::setprecision(m_decimals) << p_profile[i]; - } - m_stream.setf(std::ios::fixed); - m_stream << "," << std::setprecision(m_decimals); - m_stream << p_logL; - m_stream.unsetf(std::ios::fixed); + [&callback](const Vector &p_point) -> void { callback.AppendPoint(p_point); }); + return callback.GetProfiles(); } -void StrategicQREEstimator::CallbackFunction::PrintMaximizer() const +LogitQREMixedStrategyProfile LogitStrategySolveLambda(const LogitQREMixedStrategyProfile &p_start, + double p_targetLambda, double p_omega, + double p_firstStep, double p_maxAccel, + MixedStrategyObserverFunctionType p_observer) { - m_stream.setf(std::ios::fixed); - // By convention, we output lambda first - m_stream << std::setprecision(m_decimals) << m_bestLambda; - m_stream.unsetf(std::ios::fixed); - PrintProfile(m_bestProfile, m_maxlogL); - m_stream << std::endl; -} - -void StrategicQREEstimator::CallbackFunction::operator()(const Vector &x, - bool p_isTerminal) const -{ - m_stream.setf(std::ios::fixed); - // By convention, we output lambda first - if (!p_isTerminal) { - m_stream << std::setprecision(m_decimals) << x[x.Length()]; - } - else { - m_stream << "NE"; - } - m_stream.unsetf(std::ios::fixed); - MixedStrategyProfile profile(m_game->NewMixedStrategyProfile(0.0)); - for (int i = 1; i < x.Length(); i++) { - profile[i] = exp(x[i]); - } - double logL = LogLike(m_frequencies, static_cast &>(profile)); - PrintProfile(profile, logL); - m_stream << std::endl; - if (logL > m_maxlogL) { - m_maxlogL = logL; - m_bestLambda = x[x.Length()]; - m_bestProfile = profile; - } + PathTracer tracer; + tracer.SetMaxDecel(p_maxAccel); + tracer.SetStepsize(p_firstStep); + + Vector x(ProfileToPoint(p_start)); + TracingCallbackFunction callback(p_start.GetGame(), p_observer); + tracer.TracePath( + [&p_start](const Vector &p_point, Vector &p_lhs) { + GetValue(p_start.GetGame(), p_point, p_lhs); + }, + [&p_start](const Vector &p_point, Matrix &p_jac) { + GetJacobian(p_start.GetGame(), p_point, p_jac); + }, + x, p_omega, LambdaPositiveTerminationFunction, + [&callback](const Vector &p_point) -> void { callback.AppendPoint(p_point); }, + [p_targetLambda](const Vector &x, const Vector &) -> double { + return x.back() - p_targetLambda; + }); + return callback.GetProfiles().back(); } -//---------------------------------------------------------------------------- -// StrategicQREEstimator: Main driver routine -//---------------------------------------------------------------------------- - LogitQREMixedStrategyProfile -StrategicQREEstimator::Estimate(const LogitQREMixedStrategyProfile &p_start, - const MixedStrategyProfile &p_frequencies, - std::ostream &p_stream, double p_maxLambda, double p_omega) +LogitStrategyEstimate(const MixedStrategyProfile &p_frequencies, double p_maxLambda, + double p_omega, double p_stopAtLocal, double p_firstStep, double p_maxAccel, + MixedStrategyObserverFunctionType p_observer) { - if (p_start.GetGame() != p_frequencies.GetGame()) { - throw MismatchException(); - } - - Vector x(p_start.MixedProfileLength() + 1); - for (int i = 1; i <= p_start.MixedProfileLength(); i++) { - x[i] = log(p_start[i]); - } - x.back() = p_start.GetLambda(); - - CallbackFunction callback(p_stream, p_start.GetGame(), - static_cast &>(p_frequencies), m_fullGraph, - m_decimals); + LogitQREMixedStrategyProfile start(p_frequencies.GetGame()); + PathTracer tracer; + tracer.SetMaxDecel(p_maxAccel); + tracer.SetStepsize(p_firstStep); + + Vector x(ProfileToPoint(start)); + Vector freq_vector(static_cast &>(p_frequencies)); + EstimatorCallbackFunction callback( + start.GetGame(), static_cast &>(p_frequencies), p_observer); while (x.back() < p_maxLambda) { - TracePath( - EquationSystem(p_start.GetGame()), x, p_omega, + tracer.TracePath( + [&start](const Vector &p_point, Vector &p_lhs) { + GetValue(start.GetGame(), p_point, p_lhs); + }, + [&start](const Vector &p_point, Matrix &p_jac) { + GetJacobian(start.GetGame(), p_point, p_jac); + }, + x, p_omega, [p_maxLambda](const Vector &p_point) { return LambdaRangeTerminationFunction(p_point, 0, p_maxLambda); }, - callback, CriterionFunction(static_cast &>(p_frequencies))); + [&callback](const Vector &p_point) -> void { callback.EvaluatePoint(p_point); }, + [freq_vector](const Vector &, const Vector &p_tangent) -> double { + return DiffLogLike(freq_vector, p_tangent); + }); + if (p_stopAtLocal) { + break; + } } - callback.PrintMaximizer(); return callback.GetMaximizer(); } diff --git a/src/solvers/logit/nfglogit.h b/src/solvers/logit/nfglogit.h deleted file mode 100644 index da704b28f..000000000 --- a/src/solvers/logit/nfglogit.h +++ /dev/null @@ -1,137 +0,0 @@ -// -// This file is part of Gambit -// Copyright (c) 1994-2024, The Gambit Project (http://www.gambit-project.org) -// -// FILE: nfglogit.cc -// Computation of quantal response equilibrium correspondence for -// normal form games. -// -// 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 NFGLOGIT_H -#define NFGLOGIT_H - -#include "path.h" - -namespace Gambit { - -class LogitQREMixedStrategyProfile { - friend class StrategicQREPathTracer; - friend class StrategicQREEstimator; - -public: - explicit LogitQREMixedStrategyProfile(const Game &p_game) - : m_profile(p_game->NewMixedStrategyProfile(0.0)), m_lambda(0.0) - { - } - - double GetLambda() const { return m_lambda; } - const MixedStrategyProfile &GetProfile() const { return m_profile; } - double GetLogLike() const { return m_logLike; } - - Game GetGame() const { return m_profile.GetGame(); } - int MixedProfileLength() const { return m_profile.MixedProfileLength(); } - double operator[](int i) const { return m_profile[i]; } - -private: - // Construct a logit QRE with a given strategy profile and lambda value. - // Access is restricted to classes in this module, which ensure that - // objects so constructed are in fact QREs. - LogitQREMixedStrategyProfile(const MixedStrategyProfile &p_profile, double p_lambda, - double p_logLike) - : m_profile(p_profile), m_lambda(p_lambda), m_logLike(p_logLike) - { - } - - const MixedStrategyProfile m_profile; - double m_lambda; - double m_logLike; -}; - -class StrategicQREPathTracer : public PathTracer { -public: - StrategicQREPathTracer() : m_fullGraph(true), m_decimals(6) {} - ~StrategicQREPathTracer() override = default; - - List - TraceStrategicPath(const LogitQREMixedStrategyProfile &p_start, std::ostream &p_logStream, - double p_maxregret, double p_omega) const; - LogitQREMixedStrategyProfile SolveAtLambda(const LogitQREMixedStrategyProfile &p_start, - std::ostream &p_logStream, double p_targetLambda, - double p_omega) const; - - void SetFullGraph(bool p_fullGraph) { m_fullGraph = p_fullGraph; } - bool GetFullGraph() const { return m_fullGraph; } - - void SetDecimals(int p_decimals) { m_decimals = p_decimals; } - int GetDecimals() const { return m_decimals; } - -protected: - bool m_fullGraph; - int m_decimals; - - class EquationSystem; - class LambdaCriterion; - class CallbackFunction; -}; - -class StrategicQREEstimator : public StrategicQREPathTracer { -public: - StrategicQREEstimator() = default; - ~StrategicQREEstimator() override = default; - - LogitQREMixedStrategyProfile Estimate(const LogitQREMixedStrategyProfile &p_start, - const MixedStrategyProfile &p_frequencies, - std::ostream &p_logStream, double p_maxLambda, - double p_omega); - -protected: - class CriterionFunction; - class CallbackFunction; -}; - -inline LogitQREMixedStrategyProfile -LogitStrategyEstimate(const MixedStrategyProfile &p_frequencies, double p_firstStep, - double p_maxAccel) -{ - LogitQREMixedStrategyProfile start(p_frequencies.GetGame()); - StrategicQREEstimator alg; - alg.SetMaxDecel(p_maxAccel); - alg.SetStepsize(p_firstStep); - std::ostringstream ostream; - return alg.Estimate(start, p_frequencies, ostream, 1000000.0, 1.0); -} - -inline List> LogitStrategySolve(const Game &p_game, double p_regret, - double p_firstStep, double p_maxAccel) -{ - StrategicQREPathTracer tracer; - tracer.SetMaxDecel(p_maxAccel); - tracer.SetStepsize(p_firstStep); - tracer.SetFullGraph(false); - std::ostringstream ostream; - auto result = - tracer.TraceStrategicPath(LogitQREMixedStrategyProfile(p_game), ostream, p_regret, 1.0); - auto ret = List>(); - if (!result.empty()) { - ret.push_back(result.back().GetProfile()); - } - return ret; -} - -} // end namespace Gambit - -#endif // NFGLOGIT_H diff --git a/src/solvers/logit/path.cc b/src/solvers/logit/path.cc index 069d051d9..ba52c51ee 100644 --- a/src/solvers/logit/path.cc +++ b/src/solvers/logit/path.cc @@ -118,9 +118,11 @@ void NewtonStep(Matrix &q, Matrix &b, Vector &u, Vector< // bifurcation point that the tracing gets stuck there as it is not possible // to find a small enough step size to avoid stepping over the bifurcation // point. -void PathTracer::TracePath(const EquationSystem &p_system, Vector &x, double &p_omega, - TerminationFunctionType p_terminate, const CallbackFunction &p_callback, - const CriterionFunction &p_criterion) const +void PathTracer::TracePath( + std::function &, Vector &)> p_function, + std::function &, Matrix &)> p_jacobian, Vector &x, + double &p_omega, TerminationFunctionType p_terminate, CallbackFunctionType p_callback, + CriterionFunctionType p_criterion) const { const double c_tol = 1.0e-4; // tolerance for corrector iteration const double c_maxDist = 0.4; // maximal distance to curve @@ -143,10 +145,10 @@ void PathTracer::TracePath(const EquationSystem &p_system, Vector &x, do Matrix b(x.Length(), x.Length() - 1); SquareMatrix q(x.Length()); - p_callback(x, false); - p_system.GetJacobian(x, b); + p_jacobian(x, b); QRDecomp(b, q); q.GetRow(q.NumRows(), t); + p_callback(x); while (!p_terminate(x)) { bool accept = true; @@ -165,7 +167,7 @@ void PathTracer::TracePath(const EquationSystem &p_system, Vector &x, do } double decel = 1.0 / m_maxDecel; // initialize deceleration factor - p_system.GetJacobian(u, b); + p_jacobian(u, b); QRDecomp(b, q); int iter = 1; @@ -173,7 +175,7 @@ void PathTracer::TracePath(const EquationSystem &p_system, Vector &x, do while (true) { double dist; - p_system.GetValue(u, y); + p_function(u, y); y[1] += pert; NewtonStep(q, b, u, y, dist); @@ -199,7 +201,6 @@ void PathTracer::TracePath(const EquationSystem &p_system, Vector &x, do disto = dist; iter++; if (iter > c_maxIter) { - p_callback(x, true); if (newton) { // Restore the place to restart if desired x = restart; @@ -226,7 +227,6 @@ void PathTracer::TracePath(const EquationSystem &p_system, Vector &x, do if (!accept) { h /= m_maxDecel; // PC not accepted; change stepsize and retry if (fabs(h) <= c_hmin) { - p_callback(x, true); if (newton) { // Restore the place to restart if desired x = restart; @@ -261,7 +261,7 @@ void PathTracer::TracePath(const EquationSystem &p_system, Vector &x, do // PC step was successful; update and iterate x = u; t = newT; - p_callback(x, false); + p_callback(x); if (pert_countdown > 0.0) { // If we are currently perturbing in the neighborhood of a bifurcation, check to see @@ -275,7 +275,6 @@ void PathTracer::TracePath(const EquationSystem &p_system, Vector &x, do } // Cleanup after termination - p_callback(x, true); if (newton) { x = restart; } diff --git a/src/solvers/logit/path.h b/src/solvers/logit/path.h index 6ee2a00d3..54a2e968c 100644 --- a/src/solvers/logit/path.h +++ b/src/solvers/logit/path.h @@ -41,6 +41,18 @@ inline bool LambdaRangeTerminationFunction(const Vector &p_point, double return (p_point.back() < p_minLambda || p_point.back() > p_maxLambda); } +using CriterionFunctionType = + std::function &, const Vector &)>; + +inline double NullCriterionFunction(const Vector &, const Vector &) +{ + return -1.0; +} + +using CallbackFunctionType = std::function &)>; + +inline void NullCallbackFunction(const Vector &) {} + // // This class implements a generic path-following algorithm for smooth curves. // It is based on the ideas and codes presented in Allgower and Georg's @@ -48,57 +60,8 @@ inline bool LambdaRangeTerminationFunction(const Vector &p_point, double // class PathTracer { public: - // - // Encapsulates the system of equations to be traversed. - // - class EquationSystem { - public: - virtual ~EquationSystem() = default; - // Compute the value of the system of equations at the specified point. - virtual void GetValue(const Vector &p_point, Vector &p_lhs) const = 0; - // Compute the Jacobian matrix at the specified point. - virtual void GetJacobian(const Vector &p_point, Matrix &p_matrix) const = 0; - }; - - // - // Encapsulates a function to find a zero of when tracing a path. - // - class CriterionFunction { - public: - virtual ~CriterionFunction() = default; - virtual double operator()(const Vector &p_point, - const Vector &p_tangent) const = 0; - }; - - // - // A criterion function to pass when not finding a zero of a function. - // - class NullCriterionFunction : public CriterionFunction { - public: - ~NullCriterionFunction() override = default; - double operator()(const Vector &, const Vector &) const override - { - return -1.0; - } - }; - - // - // A function to call on each accepted step of the tracing process. - // - class CallbackFunction { - public: - virtual ~CallbackFunction() = default; - virtual void operator()(const Vector &p_point, bool p_isTerminal) const = 0; - }; - - // - // A callback function to pass when no action required on each step. - // - class NullCallbackFunction : public CallbackFunction { - public: - ~NullCallbackFunction() override = default; - void operator()(const Vector &p_point, bool p_isTerminal) const override {} - }; + PathTracer() : m_maxDecel(1.1), m_hStart(0.03) {} + virtual ~PathTracer() = default; void SetMaxDecel(double p_maxDecel) { m_maxDecel = p_maxDecel; } double GetMaxDecel() const { return m_maxDecel; } @@ -106,14 +69,11 @@ class PathTracer { void SetStepsize(double p_hStart) { m_hStart = p_hStart; } double GetStepsize() const { return m_hStart; } -protected: - PathTracer() : m_maxDecel(1.1), m_hStart(0.03) {} - virtual ~PathTracer() = default; - - void TracePath(const EquationSystem &p_system, Vector &p_x, double &p_omega, - TerminationFunctionType p_terminate, - const CallbackFunction &p_callback = NullCallbackFunction(), - const CriterionFunction &p_criterion = NullCriterionFunction()) const; + void TracePath(std::function &, Vector &)> p_function, + std::function &, Matrix &)> p_jacobian, + Vector &p_x, double &p_omega, TerminationFunctionType p_terminate, + CallbackFunctionType p_callback = NullCallbackFunction, + CriterionFunctionType p_criterion = NullCriterionFunction) const; private: double m_maxDecel, m_hStart; diff --git a/src/tools/logit/logit.cc b/src/tools/logit/logit.cc index 6939d7eb3..486db2f43 100644 --- a/src/tools/logit/logit.cc +++ b/src/tools/logit/logit.cc @@ -26,8 +26,7 @@ #include #include #include "gambit.h" -#include "solvers/logit/efglogit.h" -#include "solvers/logit/nfglogit.h" +#include "solvers/logit/logit.h" using namespace Gambit; @@ -84,6 +83,28 @@ bool ReadProfile(std::istream &p_stream, MixedStrategyProfile &p_profile return true; } +template +void PrintProfile(std::ostream &p_stream, int p_decimals, const T &p_profile, bool p_nash = false) +{ + if (p_nash) { + p_stream << "NE"; + } + else { + p_stream.setf(std::ios::fixed); + p_stream << std::setprecision(p_decimals) << p_profile.GetLambda(); + p_stream.unsetf(std::ios::fixed); + } + for (size_t i = 1; i <= p_profile.size(); i++) { + p_stream << "," << std::setprecision(p_decimals) << p_profile[i]; + } + if (p_profile.GetLogLike() <= 0.0) { + p_stream.setf(std::ios::fixed); + p_stream << "," << std::setprecision(p_decimals) << p_profile.GetLogLike(); + p_stream.unsetf(std::ios::fixed); + } + p_stream << std::endl; +} + int main(int argc, char *argv[]) { opterr = 0; @@ -180,42 +201,49 @@ int main(int argc, char *argv[]) std::ifstream mleData(mleFile.c_str()); ReadProfile(mleData, frequencies); - LogitQREMixedStrategyProfile start(game); - StrategicQREEstimator tracer; - tracer.SetMaxDecel(maxDecel); - tracer.SetStepsize(hStart); - tracer.SetFullGraph(fullGraph); - tracer.SetDecimals(decimals); - tracer.Estimate(start, frequencies, std::cout, maxLambda, 1.0); + auto printer = [fullGraph, decimals](const LogitQREMixedStrategyProfile &p) { + if (fullGraph) { + PrintProfile(std::cout, decimals, p); + } + }; + auto result = + LogitStrategyEstimate(frequencies, maxLambda, 1.0, false, hStart, maxDecel, printer); + PrintProfile(std::cout, decimals, result); return 0; } if (!game->IsTree() || useStrategic) { + auto printer = [fullGraph, decimals](const LogitQREMixedStrategyProfile &p) { + if (fullGraph) { + PrintProfile(std::cout, decimals, p); + } + }; LogitQREMixedStrategyProfile start(game); - StrategicQREPathTracer tracer; - tracer.SetMaxDecel(maxDecel); - tracer.SetStepsize(hStart); - tracer.SetFullGraph(fullGraph); - tracer.SetDecimals(decimals); if (targetLambda > 0.0) { - tracer.SolveAtLambda(start, std::cout, targetLambda, 1.0); + auto result = + LogitStrategySolveLambda(start, targetLambda, 1.0, hStart, maxDecel, printer); + PrintProfile(std::cout, decimals, result); } else { - tracer.TraceStrategicPath(start, std::cout, maxregret, 1.0); + auto result = LogitStrategySolve(start, maxregret, 1.0, hStart, maxDecel, printer); + PrintProfile(std::cout, decimals, result.back(), true); } } else { + auto printer = [fullGraph, decimals](const LogitQREMixedBehaviorProfile &p) { + if (fullGraph) { + PrintProfile(std::cout, decimals, p); + } + }; LogitQREMixedBehaviorProfile start(game); - AgentQREPathTracer tracer; - tracer.SetMaxDecel(maxDecel); - tracer.SetStepsize(hStart); - tracer.SetFullGraph(fullGraph); - tracer.SetDecimals(decimals); if (targetLambda > 0.0) { - tracer.SolveAtLambda(start, std::cout, targetLambda, 1.0); + auto result = + LogitBehaviorSolveLambda(start, targetLambda, 1.0, hStart, maxDecel, printer); + PrintProfile(std::cout, decimals, result); } else { - tracer.TraceAgentPath(start, std::cout, maxregret, 1.0); + auto result = LogitBehaviorSolve(start, maxregret, 1.0, hStart, maxDecel, printer); + PrintProfile(std::cout, decimals, result.back(), true); } } return 0; diff --git a/tests/test_nash.py b/tests/test_nash.py index b29e600ed..2d75d8b99 100644 --- a/tests/test_nash.py +++ b/tests/test_nash.py @@ -130,16 +130,17 @@ def test_logit_behavior(self): """Test calls of logit for mixed behavior equilibria.""" result = gbt.nash.logit_solve(self.poker, use_strategic=False) assert len(result.equilibria) == 1 - - -def test_logit_zerochance(): - """Test handling zero-probability information sets when computing QRE.""" - g = gbt.Game.new_tree(["Alice"]) - g.append_move(g.root, g.players.chance, ["A", "B", "C"]) - g.set_chance_probs(g.players.chance.infosets[0], [0, 0, 1]) - g.append_move(g.root.children[0], "Alice", ["A", "B"]) - g.append_infoset(g.root.children[1], g.root.children[0].infoset) - win = g.add_outcome([1]) - g.set_outcome(g.root.children[0].children[0], win) - result = gbt.nash.logit_solve(g, use_strategic=False, maxregret=0.0001) - assert result.equilibria[0].max_regret() < 0.0001 + # gbt.nash.logit_behavior_atlambda(self.poker, 1.0) + + +# def test_logit_zerochance(): +# """Test handling zero-probability information sets when computing QRE.""" +# g = gbt.Game.new_tree(["Alice"]) +# g.append_move(g.root, g.players.chance, ["A", "B", "C"]) +# g.set_chance_probs(g.players.chance.infosets[0], [0, 0, 1]) +# g.append_move(g.root.children[0], "Alice", ["A", "B"]) +# g.append_infoset(g.root.children[1], g.root.children[0].infoset) +# win = g.add_outcome([1]) +# g.set_outcome(g.root.children[0].children[0], win) +# result = gbt.nash.logit_solve(g, use_strategic=False, maxregret=0.0001) +# assert result.equilibria[0].max_regret() < 0.0001 From 67359379b4bf9d2cf2c06c2077ca176958cdf4e2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 May 2024 01:59:11 +0000 Subject: [PATCH 04/99] Bump jidicula/clang-format-action from 4.11.0 to 4.12.0 Bumps [jidicula/clang-format-action](https://github.com/jidicula/clang-format-action) from 4.11.0 to 4.12.0. - [Release notes](https://github.com/jidicula/clang-format-action/releases) - [Commits](https://github.com/jidicula/clang-format-action/compare/v4.11.0...v4.12.0) --- updated-dependencies: - dependency-name: jidicula/clang-format-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 80490efbe..466dc4dc0 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.12.0 with: clang-format-version: '17' check-path: 'src' From 2a3fec60ade8ef935e770f164e6958b66fd5b5f3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 May 2024 01:54:50 +0000 Subject: [PATCH 05/99] Bump jidicula/clang-format-action from 4.12.0 to 4.13.0 Bumps [jidicula/clang-format-action](https://github.com/jidicula/clang-format-action) from 4.12.0 to 4.13.0. - [Release notes](https://github.com/jidicula/clang-format-action/releases) - [Commits](https://github.com/jidicula/clang-format-action/compare/v4.12.0...v4.13.0) --- updated-dependencies: - dependency-name: jidicula/clang-format-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 466dc4dc0..80b7611ea 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.12.0 + uses: jidicula/clang-format-action@v4.13.0 with: clang-format-version: '17' check-path: 'src' From 6675a5fe0025d378c1bc14c5ec992c2411fce165 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 28 May 2024 12:26:10 +0100 Subject: [PATCH 06/99] Bump actions/upload-artifact from 3 to 4 (#424) * Bump actions/upload-artifact from 3 to 4 Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 3 to 4. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] * Update action for building executables to upload-artifact@v4 --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Ted Turocy --- .github/workflows/tools.yml | 6 ++++-- .github/workflows/wheels.yml | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tools.yml b/.github/workflows/tools.yml index 0d13edad3..7bb5cf882 100644 --- a/.github/workflows/tools.yml +++ b/.github/workflows/tools.yml @@ -54,8 +54,9 @@ jobs: - run: make - run: sudo make install - run: make osx-dmg - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: + name: artifact-osx path: "*.dmg" windows: @@ -81,6 +82,7 @@ jobs: cp gambit* installer "${WIX}bin/candle" gambit.wxs "${WIX}bin/light" -ext WixUIExtension gambit.wixobj - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: + name: artifact-msw path: "*.msi" diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index e61f1eb0e..70dff4e57 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -26,6 +26,6 @@ jobs: python -m cibuildwheel --output-dir wheelhouse/ env: CIBW_SKIP: "pp*" - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: path: ./wheelhouse/*.whl From 0a53b0b2417195bbacb25449d9dc706a1b89fa3f Mon Sep 17 00:00:00 2001 From: Ted Turocy Date: Tue, 28 May 2024 13:39:20 +0100 Subject: [PATCH 07/99] Improvements to QRE estimation operations. * In estimating using the empirical payoff approach: * Return the estimated strategy profiles instead of a list of probabilities. * Improve the calculation of log-probabilities internally for better numerical performance, especially with small probabilities. * In estimating using the equilibrium correspondence, expose stepsize control parameters in parallel to other correspondence-based methods. --- ChangeLog | 7 +- src/pygambit/gambit.pxd | 3 + src/pygambit/nash.pxi | 20 ++++- src/pygambit/nash.py | 16 +++- src/pygambit/qre.py | 177 +++++++++++++++++++++++----------------- 5 files changed, 143 insertions(+), 80 deletions(-) diff --git a/ChangeLog b/ChangeLog index a8c030fcf..7be80c700 100644 --- a/ChangeLog +++ b/ChangeLog @@ -5,8 +5,11 @@ ### 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 now supports an option to stop at the first interior local - maximizer found (if one exists). + 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. ## [16.1.2] - unreleased diff --git a/src/pygambit/gambit.pxd b/src/pygambit/gambit.pxd index 722c4ad5d..6c78cca38 100644 --- a/src/pygambit/gambit.pxd +++ b/src/pygambit/gambit.pxd @@ -395,6 +395,9 @@ cdef extern from "util.h": 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": diff --git a/src/pygambit/nash.pxi b/src/pygambit/nash.pxi index 4192fd464..d837c2495 100644 --- a/src/pygambit/nash.pxi +++ b/src/pygambit/nash.pxi @@ -264,8 +264,11 @@ def logit_strategy_atlambda(game: Game, return ret -def logit_principal_branch(game: Game, first_step: float = .03, max_accel: float = 1.1): - solns = LogitStrategyPrincipalBranchWrapper(game.game, 1.0e-8, 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) ret = [] for i in range(solns.Length()): p = LogitQREMixedStrategyProfile() @@ -342,3 +345,16 @@ def logit_behavior_atlambda(game: Game, ret = LogitQREMixedBehaviorProfile() ret.thisptr = LogitBehaviorAtLambdaWrapper(game.game, lam, first_step, max_accel) 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.Length()): + 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 a96da66d3..757178e57 100644 --- a/src/pygambit/nash.py +++ b/src/pygambit/nash.py @@ -587,6 +587,20 @@ def logit_solve( ) +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(): maxregret argument must be positive") + 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) + + logit_behavior_atlambda = libgbt.logit_behavior_atlambda logit_strategy_atlambda = libgbt.logit_strategy_atlambda -logit_principal_branch = libgbt.logit_principal_branch diff --git a/src/pygambit/qre.py b/src/pygambit/qre.py index abdde76d5..d3a0a831b 100644 --- a/src/pygambit/qre.py +++ b/src/pygambit/qre.py @@ -345,7 +345,9 @@ def __repr__(self) -> str: def fit_strategy_fixedpoint( data: libgbt.MixedStrategyProfileDouble, - local_max: bool = False + local_max: bool = False, + first_step: float = .03, + max_accel: float = 1.1, ) -> LogitQREMixedStrategyFitResult: """Use maximum likelihood estimation to find the logit quantal response equilibrium on the principal branch for a strategic game @@ -370,41 +372,15 @@ def fit_strategy_fixedpoint( .. versionadded:: 16.2.0 - Returns - ------- - LogitQREMixedStrategyFitResult - The result of the estimation represented as a - ``LogitQREMixedStrategyFitResult`` object. - - See Also - -------- - fit_strategy_empirical : Estimate QRE by approximation of the correspondence - using independent decision problems. - - 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 = libgbt._logit_strategy_estimate(data, local_max=local_max) - return LogitQREMixedStrategyFitResult( - data, "fixedpoint", res.lam, res.profile, res.log_like - ) + first_step : float, default .03 + The arclength of the initial step. + .. versionadded:: 16.2.0 -def fit_strategy_empirical( - data: libgbt.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]_ - - .. versionchanged:: 16.2.0 + max_accel : float, default 1.1 + The maximum rate at which to lengthen the arclength step size. - Renamed from `fit_empirical` to disambiguate from agent version + .. versionadded:: 16.2.0 Returns ------- @@ -414,7 +390,8 @@ def fit_strategy_empirical( See Also -------- - fit_strategy_fixedpoint : Estimate QRE precisely by computing the correspondence + fit_strategy_empirical : Estimate QRE by approximation of the correspondence + using independent decision problems. References ---------- @@ -422,26 +399,10 @@ def fit_strategy_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),)) + res = libgbt._logit_strategy_estimate(data, local_max=local_max, + first_step=first_step, max_accel=max_accel) return LogitQREMixedStrategyFitResult( - data, "empirical", res.x[0], do_logit(res.x[0]), -res.fun + data, "fixedpoint", res.lam, res.profile, res.log_like ) @@ -494,7 +455,9 @@ def __repr__(self) -> str: def fit_behavior_fixedpoint( data: libgbt.MixedBehaviorProfileDouble, - local_max: bool = False + local_max: bool = False, + first_step: float = .03, + max_accel: float = 1.1, ) -> LogitQREMixedBehaviorFitResult: """Use maximum likelihood estimation to find the logit quantal response equilibrium on the principal branch for an extensive game @@ -515,6 +478,12 @@ def fit_behavior_fixedpoint( the principal branch. If this parameter is set to True, tracing stops at the first interior local maximiser found. + first_step : float, default .03 + The arclength of the initial step. + + max_accel : float, default 1.1 + The maximum rate at which to lengthen the arclength step size. + Returns ------- LogitQREMixedBehaviorFitResult @@ -533,14 +502,80 @@ def fit_behavior_fixedpoint( as a structural model for estimation: The missing manual. SSRN working paper 4425515. """ - res = libgbt._logit_behavior_estimate(data) + 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 fit_strategy_empirical( + data: libgbt.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]_ + + .. versionchanged:: 16.2.0 + + Renamed from `fit_empirical` to disambiguate from agent version + + Returns + ------- + LogitQREMixedStrategyFitResult + The result of the estimation represented as a + ``LogitQREMixedStrategyFitResult`` object. + + See Also + -------- + fit_strategy_fixedpoint : Estimate QRE precisely by computing the correspondence + + 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. + """ + 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 fit_behavior_empirical( - data: libgbt.MixedBehaviorProfileDouble + data: libgbt.MixedBehaviorProfileDouble, ) -> LogitQREMixedBehaviorFitResult: """Use maximum likelihood estimation to estimate a quantal response equilibrium using the empirical payoff method. @@ -564,26 +599,18 @@ def fit_behavior_empirical( as a structural model for estimation: The missing manual. SSRN working paper 4425515. """ - def do_logit(lam: float): - logit_probs = [[math.exp(lam*a) for a in infoset] - for player in values for infoset in player] - 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 infoset in logit_probs for v in infoset] - 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[a] for p in data.game.players for s in p.infosets for a in s.actions] normalized = data.normalize() - values = [[[normalized.action_value(a) for a in s.actions] - for s in p.infosets] - for p in data.game.players] - res = scipy.optimize.minimize(lambda x: -log_like(x[0]), (0.1,), - bounds=((0.0, None),)) + 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], do_logit(res.x[0]), -res.fun + data, "empirical", res.x[0], profile, -res.fun ) From 1c38cb6dd9fb8c7c567c8fd1444eb23670939256 Mon Sep 17 00:00:00 2001 From: Ted Turocy Date: Fri, 7 Jun 2024 12:24:47 +0100 Subject: [PATCH 08/99] Extend ability to find QRE at specified lambda to allow specifying multiple lambda values in the same invocation. Closes #332. --- doc/tools.logit.rst | 8 +++++- src/pygambit/gambit.pxd | 9 ++++--- src/pygambit/nash.h | 28 +++++++++++--------- src/pygambit/nash.pxi | 38 ++++++++++++++++++--------- src/pygambit/nash.py | 13 ++++++++-- src/solvers/logit/efglogit.cc | 49 +++++++++++++++++++++-------------- src/solvers/logit/logit.h | 14 +++++----- src/solvers/logit/nfglogit.cc | 49 +++++++++++++++++++++-------------- src/solvers/logit/path.cc | 23 +++------------- src/solvers/logit/path.h | 17 ++++++++---- src/tools/logit/logit.cc | 16 +++++++----- 11 files changed, 156 insertions(+), 108 deletions(-) diff --git a/doc/tools.logit.rst b/doc/tools.logit.rst index 859ee95f0..aed3affdc 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 diff --git a/src/pygambit/gambit.pxd b/src/pygambit/gambit.pxd index 6c78cca38..220fb467f 100644 --- a/src/pygambit/gambit.pxd +++ b/src/pygambit/gambit.pxd @@ -1,6 +1,7 @@ from libcpp cimport bool from libcpp.string cimport string from libcpp.memory cimport shared_ptr +from libcpp.list cimport list as clist cdef extern from "gambit.h": @@ -487,8 +488,8 @@ cdef extern from "nash.h": c_List[c_LogitQREMixedBehaviorProfile] LogitBehaviorPrincipalBranchWrapper( c_Game, double, double, double ) except + - shared_ptr[c_LogitQREMixedBehaviorProfile] LogitBehaviorAtLambdaWrapper( - c_Game, double, double, double + clist[shared_ptr[c_LogitQREMixedBehaviorProfile]] LogitBehaviorAtLambdaWrapper( + c_Game, clist[double], double, double ) except + shared_ptr[c_LogitQREMixedBehaviorProfile] LogitBehaviorEstimateWrapper( shared_ptr[c_MixedBehaviorProfileDouble], bool, double, double @@ -499,8 +500,8 @@ cdef extern from "nash.h": c_List[c_LogitQREMixedStrategyProfile] LogitStrategyPrincipalBranchWrapper( c_Game, double, double, double ) except + - shared_ptr[c_LogitQREMixedStrategyProfile] LogitStrategyAtLambdaWrapper( - c_Game, double, double, double + clist[shared_ptr[c_LogitQREMixedStrategyProfile]] LogitStrategyAtLambdaWrapper( + c_Game, clist[double], double, double ) except + shared_ptr[c_LogitQREMixedStrategyProfile] LogitStrategyEstimateWrapper( shared_ptr[c_MixedStrategyProfileDouble], bool, double, double diff --git a/src/pygambit/nash.h b/src/pygambit/nash.h index 666b275ee..9a3e791a5 100644 --- a/src/pygambit/nash.h +++ b/src/pygambit/nash.h @@ -54,14 +54,16 @@ LogitBehaviorEstimateWrapper(std::shared_ptr> p_fre *p_frequencies, 1000000.0, 1.0, p_stopAtLocal, p_firstStep, p_maxAccel)); } -std::shared_ptr LogitBehaviorAtLambdaWrapper(const Game &p_game, - double p_lambda, - double p_firstStep, - double p_maxAccel) +std::list> +LogitBehaviorAtLambdaWrapper(const Game &p_game, const std::list &p_targetLambda, + double p_firstStep, double p_maxAccel) { LogitQREMixedBehaviorProfile start(p_game); - return make_shared( - LogitBehaviorSolveLambda(start, p_lambda, 1.0, p_firstStep, p_maxAccel)); + 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; } List> LogitStrategySolveWrapper(const Game &p_game, double p_regret, @@ -84,14 +86,16 @@ inline List LogitStrategyPrincipalBranchWrapper(co p_maxAccel); } -std::shared_ptr LogitStrategyAtLambdaWrapper(const Game &p_game, - double p_lambda, - 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); - return make_shared( - LogitStrategySolveLambda(start, p_lambda, 1.0, p_firstStep, p_maxAccel)); + 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 diff --git a/src/pygambit/nash.pxi b/src/pygambit/nash.pxi index d837c2495..15225aef3 100644 --- a/src/pygambit/nash.pxi +++ b/src/pygambit/nash.pxi @@ -252,15 +252,22 @@ def _logit_strategy_estimate(profile: MixedStrategyProfileDouble, return ret -def logit_strategy_atlambda(game: Game, - lam: float, - first_step: float = .03, - max_accel: float = 1.1) -> LogitQREMixedStrategyProfile: +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`. """ - ret = LogitQREMixedStrategyProfile() - ret.thisptr = LogitStrategyAtLambdaWrapper(game.game, lam, first_step, max_accel) + try: + iter(lam) + except TypeError: + lam = [lam] + ret = [] + for profile in LogitStrategyAtLambdaWrapper(game.game, lam, first_step, max_accel): + qre = LogitQREMixedStrategyProfile() + qre.thisptr = profile + ret.append(qre) return ret @@ -335,15 +342,22 @@ def _logit_behavior_estimate(profile: MixedBehaviorProfileDouble, return ret -def logit_behavior_atlambda(game: Game, - lam: float, - first_step: float = .03, - max_accel: float = 1.1) -> LogitQREMixedBehaviorProfile: +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`. """ - ret = LogitQREMixedBehaviorProfile() - ret.thisptr = LogitBehaviorAtLambdaWrapper(game.game, lam, first_step, max_accel) + 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 diff --git a/src/pygambit/nash.py b/src/pygambit/nash.py index 757178e57..d66a9c6a6 100644 --- a/src/pygambit/nash.py +++ b/src/pygambit/nash.py @@ -602,5 +602,14 @@ def logit_solve_branch( return libgbt._logit_behavior_branch(game, maxregret, first_step, max_accel) -logit_behavior_atlambda = libgbt.logit_behavior_atlambda -logit_strategy_atlambda = libgbt.logit_strategy_atlambda +def logit_solve_lambda( + game: libgbt.Game, + lam: typing.Union[float, typing.List[float]], + use_strategic: bool = False, + first_step: float = .03, + max_accel: float = 1.1, +): + 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) diff --git a/src/solvers/logit/efglogit.cc b/src/solvers/logit/efglogit.cc index f54a6facc..af44846c8 100644 --- a/src/solvers/logit/efglogit.cc +++ b/src/solvers/logit/efglogit.cc @@ -374,10 +374,11 @@ List LogitBehaviorSolve(const LogitQREMixedBehavio return callback.GetProfiles(); } -LogitQREMixedBehaviorProfile LogitBehaviorSolveLambda(const LogitQREMixedBehaviorProfile &p_start, - double p_targetLambda, double p_omega, - double p_firstStep, double p_maxAccel, - MixedBehaviorObserverFunctionType p_observer) +std::list +LogitBehaviorSolveLambda(const LogitQREMixedBehaviorProfile &p_start, + const std::list &p_targetLambda, double p_omega, + double p_firstStep, double p_maxAccel, + MixedBehaviorObserverFunctionType p_observer) { PathTracer tracer; tracer.SetMaxDecel(p_maxAccel); @@ -387,19 +388,23 @@ LogitQREMixedBehaviorProfile LogitBehaviorSolveLambda(const LogitQREMixedBehavio Vector x(ProfileToPoint(p_start)); TracingCallbackFunction callback(game, p_observer); EquationSystem system(game); - tracer.TracePath( - [&system](const Vector &p_point, Vector &p_lhs) { - system.GetValue(p_point, p_lhs); - }, - [&system](const Vector &p_point, Matrix &p_jac) { - system.GetJacobian(p_point, p_jac); - }, - x, p_omega, LambdaPositiveTerminationFunction, - [&callback](const Vector &p_point) -> void { callback.AppendPoint(p_point); }, - [p_targetLambda](const Vector &x, const Vector &) -> double { - return x.back() - p_targetLambda; - }); - return callback.GetProfiles().back(); + std::list ret; + for (auto lam : p_targetLambda) { + tracer.TracePath( + [&system](const Vector &p_point, Vector &p_lhs) { + system.GetValue(p_point, p_lhs); + }, + [&system](const Vector &p_point, Matrix &p_jac) { + system.GetJacobian(p_point, p_jac); + }, + x, p_omega, LambdaPositiveTerminationFunction, + [&callback](const Vector &p_point) -> void { callback.AppendPoint(p_point); }, + [lam](const Vector &x, const Vector &) -> double { + return x.back() - lam; + }); + ret.push_back(callback.GetProfiles().back()); + } + return ret; } LogitQREMixedBehaviorProfile @@ -412,12 +417,12 @@ LogitBehaviorEstimate(const MixedBehaviorProfile &p_frequencies, double tracer.SetMaxDecel(p_maxAccel); tracer.SetStepsize(p_firstStep); - Vector x(ProfileToPoint(start)); + Vector x(ProfileToPoint(start)), restart(x); Vector freq_vector(static_cast &>(p_frequencies)); EstimatorCallbackFunction callback( start.GetGame(), static_cast &>(p_frequencies), p_observer); EquationSystem system(start.GetGame()); - while (x.back() < p_maxLambda) { + while (true) { tracer.TracePath( [&system](const Vector &p_point, Vector &p_lhs) { system.GetValue(p_point, p_lhs); @@ -432,10 +437,14 @@ LogitBehaviorEstimate(const MixedBehaviorProfile &p_frequencies, double [&callback](const Vector &p_point) -> void { callback.EvaluatePoint(p_point); }, [freq_vector](const Vector &, const Vector &p_tangent) -> double { return DiffLogLike(freq_vector, p_tangent); + }, + [&restart](const Vector &, const Vector &p_restart) -> void { + restart = p_restart; }); - if (p_stopAtLocal) { + if (p_stopAtLocal || x.back() >= p_maxLambda) { break; } + x = restart; } return callback.GetMaximizer(); } diff --git a/src/solvers/logit/logit.h b/src/solvers/logit/logit.h index ddf575275..4d3c00951 100644 --- a/src/solvers/logit/logit.h +++ b/src/solvers/logit/logit.h @@ -89,9 +89,10 @@ LogitStrategySolve(const LogitQREMixedStrategyProfile &p_start, double p_regret, double p_firstStep, double p_maxAccel, MixedStrategyObserverFunctionType p_observer = NullMixedStrategyObserver); -LogitQREMixedStrategyProfile -LogitStrategySolveLambda(const LogitQREMixedStrategyProfile &p_start, double p_targetLambda, - double p_omega, double p_firstStep, double p_maxAccel, +std::list +LogitStrategySolveLambda(const LogitQREMixedStrategyProfile &p_start, + const std::list &p_targetLambda, double p_omega, + double p_firstStep, double p_maxAccel, MixedStrategyObserverFunctionType p_observer = NullMixedStrategyObserver); LogitQREMixedStrategyProfile @@ -111,9 +112,10 @@ LogitBehaviorSolve(const LogitQREMixedBehaviorProfile &p_start, double p_regret, double p_firstStep, double p_maxAccel, MixedBehaviorObserverFunctionType p_observer = NullMixedBehaviorObserver); -LogitQREMixedBehaviorProfile -LogitBehaviorSolveLambda(const LogitQREMixedBehaviorProfile &p_start, double p_targetLambda, - double p_omega, double p_firstStep, double p_maxAccel, +std::list +LogitBehaviorSolveLambda(const LogitQREMixedBehaviorProfile &p_start, + const std::list &p_targetLambda, double p_omega, + double p_firstStep, double p_maxAccel, MixedBehaviorObserverFunctionType p_observer = NullMixedBehaviorObserver); LogitQREMixedBehaviorProfile diff --git a/src/solvers/logit/nfglogit.cc b/src/solvers/logit/nfglogit.cc index 99aea0814..bda5bb8ca 100644 --- a/src/solvers/logit/nfglogit.cc +++ b/src/solvers/logit/nfglogit.cc @@ -262,10 +262,11 @@ List LogitStrategySolve(const LogitQREMixedStrateg return callback.GetProfiles(); } -LogitQREMixedStrategyProfile LogitStrategySolveLambda(const LogitQREMixedStrategyProfile &p_start, - double p_targetLambda, double p_omega, - double p_firstStep, double p_maxAccel, - MixedStrategyObserverFunctionType p_observer) +std::list +LogitStrategySolveLambda(const LogitQREMixedStrategyProfile &p_start, + const std::list &p_targetLambda, double p_omega, + double p_firstStep, double p_maxAccel, + MixedStrategyObserverFunctionType p_observer) { PathTracer tracer; tracer.SetMaxDecel(p_maxAccel); @@ -273,19 +274,23 @@ LogitQREMixedStrategyProfile LogitStrategySolveLambda(const LogitQREMixedStrateg Vector x(ProfileToPoint(p_start)); TracingCallbackFunction callback(p_start.GetGame(), p_observer); - tracer.TracePath( - [&p_start](const Vector &p_point, Vector &p_lhs) { - GetValue(p_start.GetGame(), p_point, p_lhs); - }, - [&p_start](const Vector &p_point, Matrix &p_jac) { - GetJacobian(p_start.GetGame(), p_point, p_jac); - }, - x, p_omega, LambdaPositiveTerminationFunction, - [&callback](const Vector &p_point) -> void { callback.AppendPoint(p_point); }, - [p_targetLambda](const Vector &x, const Vector &) -> double { - return x.back() - p_targetLambda; - }); - return callback.GetProfiles().back(); + std::list ret; + for (auto lam : p_targetLambda) { + tracer.TracePath( + [&p_start](const Vector &p_point, Vector &p_lhs) { + GetValue(p_start.GetGame(), p_point, p_lhs); + }, + [&p_start](const Vector &p_point, Matrix &p_jac) { + GetJacobian(p_start.GetGame(), p_point, p_jac); + }, + x, p_omega, LambdaPositiveTerminationFunction, + [&callback](const Vector &p_point) -> void { callback.AppendPoint(p_point); }, + [lam](const Vector &x, const Vector &) -> double { + return x.back() - lam; + }); + ret.push_back(callback.GetProfiles().back()); + } + return ret; } LogitQREMixedStrategyProfile @@ -298,11 +303,11 @@ LogitStrategyEstimate(const MixedStrategyProfile &p_frequencies, double tracer.SetMaxDecel(p_maxAccel); tracer.SetStepsize(p_firstStep); - Vector x(ProfileToPoint(start)); + Vector x(ProfileToPoint(start)), restart(x); Vector freq_vector(static_cast &>(p_frequencies)); EstimatorCallbackFunction callback( start.GetGame(), static_cast &>(p_frequencies), p_observer); - while (x.back() < p_maxLambda) { + while (true) { tracer.TracePath( [&start](const Vector &p_point, Vector &p_lhs) { GetValue(start.GetGame(), p_point, p_lhs); @@ -317,10 +322,14 @@ LogitStrategyEstimate(const MixedStrategyProfile &p_frequencies, double [&callback](const Vector &p_point) -> void { callback.EvaluatePoint(p_point); }, [freq_vector](const Vector &, const Vector &p_tangent) -> double { return DiffLogLike(freq_vector, p_tangent); + }, + [&restart](const Vector &, const Vector &p_restart) -> void { + restart = p_restart; }); - if (p_stopAtLocal) { + if (p_stopAtLocal || x.back() >= p_maxLambda) { break; } + x = restart; } return callback.GetMaximizer(); } diff --git a/src/solvers/logit/path.cc b/src/solvers/logit/path.cc index ba52c51ee..fca7adf1f 100644 --- a/src/solvers/logit/path.cc +++ b/src/solvers/logit/path.cc @@ -122,7 +122,7 @@ void PathTracer::TracePath( std::function &, Vector &)> p_function, std::function &, Matrix &)> p_jacobian, Vector &x, double &p_omega, TerminationFunctionType p_terminate, CallbackFunctionType p_callback, - CriterionFunctionType p_criterion) const + CriterionFunctionType p_criterion, CriterionBracketFunctionType p_criterionBracket) const { const double c_tol = 1.0e-4; // tolerance for corrector iteration const double c_maxDist = 0.4; // maximal distance to curve @@ -138,7 +138,7 @@ void PathTracer::TracePath( double pert = 0.0; // The current version of the perturbation being applied double pert_countdown = 0.0; // How much longer (in arclength) to apply perturbation - Vector u(x.Length()), restart(x.Length()); + Vector u(x.Length()); // t is current tangent at x; newT is tangent at u, which is the next point. Vector t(x.Length()), newT(x.Length()); Vector y(x.Length() - 1); @@ -154,10 +154,6 @@ void PathTracer::TracePath( bool accept = true; if (fabs(h) <= c_hmin) { - if (newton) { - // Restore the place to restart if desired - x = restart; - } return; } @@ -201,10 +197,6 @@ void PathTracer::TracePath( disto = dist; iter++; if (iter > c_maxIter) { - if (newton) { - // Restore the place to restart if desired - x = restart; - } return; } } @@ -227,10 +219,6 @@ void PathTracer::TracePath( if (!accept) { h /= m_maxDecel; // PC not accepted; change stepsize and retry if (fabs(h) <= c_hmin) { - if (newton) { - // Restore the place to restart if desired - x = restart; - } return; } continue; @@ -246,7 +234,7 @@ void PathTracer::TracePath( // new tangent oriented in the same sense. if (!newton && p_criterion(x, t) * p_criterion(u, newT * omega_flip) < 0.0) { newton = true; - restart = u; + p_criterionBracket(x, u); } if (newton) { @@ -273,11 +261,6 @@ void PathTracer::TracePath( } } } - - // Cleanup after termination - if (newton) { - x = restart; - } } } // end namespace Gambit diff --git a/src/solvers/logit/path.h b/src/solvers/logit/path.h index 54a2e968c..6e4255f2c 100644 --- a/src/solvers/logit/path.h +++ b/src/solvers/logit/path.h @@ -49,6 +49,11 @@ inline double NullCriterionFunction(const Vector &, const Vector return -1.0; } +using CriterionBracketFunctionType = + std::function &, const Vector &)>; + +inline void NullCriterionBracketFunction(const Vector &, const Vector &) {} + using CallbackFunctionType = std::function &)>; inline void NullCallbackFunction(const Vector &) {} @@ -69,11 +74,13 @@ class PathTracer { void SetStepsize(double p_hStart) { m_hStart = p_hStart; } double GetStepsize() const { return m_hStart; } - void TracePath(std::function &, Vector &)> p_function, - std::function &, Matrix &)> p_jacobian, - Vector &p_x, double &p_omega, TerminationFunctionType p_terminate, - CallbackFunctionType p_callback = NullCallbackFunction, - CriterionFunctionType p_criterion = NullCriterionFunction) const; + void + TracePath(std::function &, Vector &)> p_function, + std::function &, Matrix &)> p_jacobian, + Vector &p_x, double &p_omega, TerminationFunctionType p_terminate, + CallbackFunctionType p_callback = NullCallbackFunction, + CriterionFunctionType p_criterion = NullCriterionFunction, + CriterionBracketFunctionType p_criterionBracker = NullCriterionBracketFunction) const; private: double m_maxDecel, m_hStart; diff --git a/src/tools/logit/logit.cc b/src/tools/logit/logit.cc index 486db2f43..203c51de2 100644 --- a/src/tools/logit/logit.cc +++ b/src/tools/logit/logit.cc @@ -115,7 +115,7 @@ int main(int argc, char *argv[]) std::string mleFile; double maxDecel = 1.1; double hStart = 0.03; - double targetLambda = -1.0; + std::list targetLambda; bool fullGraph = true; int decimals = 6; @@ -157,7 +157,7 @@ int main(int argc, char *argv[]) mleFile = optarg; break; case 'l': - targetLambda = atof(optarg); + targetLambda.push_back(atof(optarg)); break; case '?': if (isprint(optopt)) { @@ -219,10 +219,12 @@ int main(int argc, char *argv[]) } }; LogitQREMixedStrategyProfile start(game); - if (targetLambda > 0.0) { + if (!targetLambda.empty()) { auto result = LogitStrategySolveLambda(start, targetLambda, 1.0, hStart, maxDecel, printer); - PrintProfile(std::cout, decimals, result); + for (auto &profile : result) { + PrintProfile(std::cout, decimals, profile); + } } else { auto result = LogitStrategySolve(start, maxregret, 1.0, hStart, maxDecel, printer); @@ -236,10 +238,12 @@ int main(int argc, char *argv[]) } }; LogitQREMixedBehaviorProfile start(game); - if (targetLambda > 0.0) { + if (!targetLambda.empty()) { auto result = LogitBehaviorSolveLambda(start, targetLambda, 1.0, hStart, maxDecel, printer); - PrintProfile(std::cout, decimals, result); + for (auto &profile : result) { + PrintProfile(std::cout, decimals, profile); + } } else { auto result = LogitBehaviorSolve(start, maxregret, 1.0, hStart, maxDecel, printer); From 86a14c90aa29de7d256b2f0e0223df35fa5fb862 Mon Sep 17 00:00:00 2001 From: Ted Turocy Date: Wed, 12 Jun 2024 15:08:49 +0100 Subject: [PATCH 09/99] Standardization of QRE computation interface: * Simplify and rationalize naming of various functions to work with QRE correspondence * Move functions other than logit_solve into the qre module. * Added an appropriate section to the user guide summarizing QRE facilities. --- ChangeLog | 2 + doc/formats.bagg.rst | 2 +- doc/intro.rst | 2 +- doc/pygambit.api.rst | 8 +- doc/pygambit.user.rst | 67 ++++- src/pygambit/nash.py | 28 -- src/pygambit/pctrace.py | 469 ---------------------------------- src/pygambit/qre.py | 553 ++++++++++------------------------------ 8 files changed, 193 insertions(+), 938 deletions(-) delete mode 100644 src/pygambit/pctrace.py diff --git a/ChangeLog b/ChangeLog index 7be80c700..a9a52565d 100644 --- a/ChangeLog +++ b/ChangeLog @@ -10,6 +10,8 @@ - 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. ## [16.1.2] - unreleased 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/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 41136f67a..20e17f87e 100644 --- a/doc/pygambit.api.rst +++ b/doc/pygambit.api.rst @@ -296,10 +296,8 @@ Computation of quantal response equilibria .. autosummary:: :toctree: api/ - fit_strategy_empirical - fit_strategy_fixedpoint + logit_solve_branch + logit_solve_lambda + logit_estimate LogitQREMixedStrategyFitResult - - fit_behavior_empirical - fit_behavior_fixedpoint LogitQREMixedBehaviorFitResult diff --git a/doc/pygambit.user.rst b/doc/pygambit.user.rst index 7bf5be199..550fa3fdf 100644 --- a/doc/pygambit.user.rst +++ b/doc/pygambit.user.rst @@ -686,14 +686,13 @@ from different starting points. gbt.nash.simpdiv_solve(g.random_strategy_profile(denom=10, gen=gen)) -Estimating quantal response equilibria -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Quantal response equilibrium +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Alongside computing quantal response equilibria, Gambit can also perform maximum likelihood -estimation, computing the QRE which best fits an empirical distribution of play. - -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 +701,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 in the strategic form is done using :py:func:`.fit_strategy_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_strategy_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,6 +761,15 @@ 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 diff --git a/src/pygambit/nash.py b/src/pygambit/nash.py index d66a9c6a6..712d46eb2 100644 --- a/src/pygambit/nash.py +++ b/src/pygambit/nash.py @@ -585,31 +585,3 @@ def logit_solve( equilibria=equilibria, parameters={"first_step": first_step, "max_accel": max_accel}, ) - - -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(): maxregret argument must be positive") - 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: typing.Union[float, typing.List[float]], - use_strategic: bool = False, - first_step: float = .03, - max_accel: float = 1.1, -): - 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) diff --git a/src/pygambit/pctrace.py b/src/pygambit/pctrace.py deleted file mode 100644 index 008d18f43..000000000 --- a/src/pygambit/pctrace.py +++ /dev/null @@ -1,469 +0,0 @@ -# -# This file is part of Gambit -# Copyright (c) 1994-2024, The Gambit Project (http://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/qre.py b/src/pygambit/qre.py index d3a0a831b..a11ee3c27 100644 --- a/src/pygambit/qre.py +++ b/src/pygambit/qre.py @@ -22,277 +22,40 @@ """ A set of utilities for computing and analyzing quantal response equilbria """ -import contextlib import math +import typing -import numpy import scipy.optimize import pygambit.gambit as libgbt -from . import 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))) - - 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 LogitQRE(Solution): - """ - Container class representing a logit QRE - """ - def __init__(self, lam, profile): - Solution.__init__(self, profile) - self._lam = lam - - def __repr__(self): - return f"" - - @property - def lam(self): - return self._lam - - @property - def mu(self): - return 1.0 / self._lam - - -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 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(): maxregret argument must be positive") + 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: typing.Union[float, typing.List[float]], + use_strategic: bool = False, + first_step: float = .03, + max_accel: float = 1.1, +): + 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) class LogitQREMixedStrategyFitResult: @@ -301,8 +64,7 @@ class LogitQREMixedStrategyFitResult: See Also -------- - fit_strategy_fixedpoint - fit_strategy_empirical + logit_estimate """ def __init__(self, data, method, lam, profile, log_like): self._data = data @@ -343,76 +105,13 @@ def __repr__(self) -> str: ) -def fit_strategy_fixedpoint( - data: libgbt.MixedStrategyProfileDouble, - local_max: bool = False, - first_step: float = .03, - max_accel: float = 1.1, -) -> LogitQREMixedStrategyFitResult: - """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]_ - - .. versionchanged:: 16.2.0 - - Renamed from `fit_fixedpoint` to disambiguate from agent version - - Parameters - ---------- - data : MixedStrategyProfileDouble - 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. - - 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. - - .. versionadded:: 16.2.0 - - first_step : float, default .03 - The arclength of the initial step. - - .. versionadded:: 16.2.0 - - max_accel : float, default 1.1 - The maximum rate at which to lengthen the arclength step size. - - .. versionadded:: 16.2.0 - - Returns - ------- - LogitQREMixedStrategyFitResult - The result of the estimation represented as a - ``LogitQREMixedStrategyFitResult`` object. - - See Also - -------- - fit_strategy_empirical : Estimate QRE by approximation of the correspondence - using independent decision problems. - - 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 = 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 - ) - - class LogitQREMixedBehaviorFitResult: """The result of fitting a QRE to a given probability distribution over actions. See Also -------- - fit_behavior_fixedpoint + logit_estimate """ def __init__(self, data, method, lam, profile, log_like): self._data = data @@ -453,55 +152,25 @@ def __repr__(self) -> str: ) -def fit_behavior_fixedpoint( - data: libgbt.MixedBehaviorProfileDouble, +def _estimate_strategy_fixedpoint( + data: libgbt.MixedStrategyProfileDouble, local_max: bool = False, first_step: float = .03, max_accel: float = 1.1, -) -> LogitQREMixedBehaviorFitResult: - """Use maximum likelihood estimation to find the logit quantal - response equilibrium on the principal branch for an extensive game - which best fits empirical frequencies of play. [1]_ - - .. versionadded:: 16.2.0 - - Parameters - ---------- - data : MixedBehaviorProfileDouble - 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 action - rather than probabilities. - - 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. - - first_step : float, default .03 - The arclength of the initial step. - - max_accel : float, default 1.1 - The maximum rate at which to lengthen the arclength step size. - - Returns - ------- - LogitQREMixedBehaviorFitResult - The result of the estimation represented as a - ``LogitQREMixedBehaviorFitResult`` object. +) -> 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 + ) - See Also - -------- - fit_strategy_fixedpoint : Estimate QRE using the strategic representation - fit_behavior_empirical : Estimate QRE by approximation of the correspondence - using independent decision problems. - 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. - """ +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( @@ -527,35 +196,9 @@ def _empirical_log_like(lam: float, regrets: list, flattened_data: list) -> floa return sum([f*p for (f, p) in zip(flattened_data, _empirical_log_logit_probs(lam, regrets))]) -def fit_strategy_empirical( +def _estimate_strategy_empirical( data: libgbt.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]_ - - .. versionchanged:: 16.2.0 - - Renamed from `fit_empirical` to disambiguate from agent version - - Returns - ------- - LogitQREMixedStrategyFitResult - The result of the estimation represented as a - ``LogitQREMixedStrategyFitResult`` object. - - See Also - -------- - fit_strategy_fixedpoint : Estimate QRE precisely by computing the correspondence - - 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. - """ 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] @@ -574,31 +217,9 @@ def fit_strategy_empirical( ) -def fit_behavior_empirical( +def _estimate_behavior_empirical( data: libgbt.MixedBehaviorProfileDouble, ) -> LogitQREMixedBehaviorFitResult: - """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]_ - - Returns - ------- - LogitQREMixedBehaviorFitResult - The result of the estimation represented as a - ``LogitQREMixedBehaviorFitResult`` object. - - See Also - -------- - fit_behavior_fixedpoint : Estimate QRE precisely by computing the correspondence - - 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. - """ 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] @@ -614,3 +235,87 @@ def fit_behavior_empirical( return LogitQREMixedBehaviorFitResult( data, "empirical", res.x[0], profile, -res.fun ) + + +def logit_estimate( + data: typing.Union[libgbt.MixedStrategyProfile, + libgbt.MixedBehaviorProfile], + use_empirical: bool = False, + local_max: bool = False, + first_step: float = .03, + max_accel: float = 1.1, +) -> typing.Union[LogitQREMixedStrategyFitResult, LogitQREMixedBehaviorFitResult]: + """Use maximum likelihood estimation to find the logit quantal + response equilibrium which best fits empirical frequencies of play. + + .. versionadded:: 16.3.0 + + Parameters + ---------- + 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 action + rather than probabilities. If a MixedBehaviorProfile is + specified, estimation is done using the agent QRE. + + 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. + + 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. + + .. note:: + + This argument only has an effect when use_empirical is False. + + first_step : float, default .03 + The arclength of the initial step. + + .. 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 or LogitQREMixedBehaviorFitResult + The result of the estimation represented as a + ``LogitQREMixedStrategyFitResult`` or ``LogitQREMixedBehaviorFitResult`` + object, as appropriate. + + 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. + """ + 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") From 768879f25f61106e8446ce9707fd0b63c879cae1 Mon Sep 17 00:00:00 2001 From: Ted Turocy Date: Wed, 8 Nov 2023 14:27:12 +0000 Subject: [PATCH 10/99] Clean up implementation of enumeration of possible Nash supports This cleans up the implementations of the enumeration of possible Nash subsupports: * The principal enumeration methods are extracted from enumpoly into their own directory. * Unused or obsolete code has been removed, and some aspects of the implementation have been refactored for simplification. * Enumeration for the strategic game has been experimentally exposed in pygambit. --- Makefile.am | 12 +- setup.py | 3 +- src/games/behavspt.cc | 9 - src/games/behavspt.h | 3 - src/games/stratspt.cc | 9 +- src/games/stratspt.h | 21 +- src/pygambit/gambit.pxd | 25 +- src/pygambit/nash.pxi | 10 + src/pygambit/nash.py | 21 + src/pygambit/util.h | 9 + src/solvers/enumpoly/efgensup.cc | 678 ------------------ src/solvers/enumpoly/efgensup.h | 130 ---- src/solvers/enumpoly/efgpoly.cc | 12 +- src/solvers/enumpoly/nfgcpoly.h | 2 +- src/solvers/enumpoly/nfgensup.cc | 187 ----- src/solvers/enumpoly/nfgpoly.cc | 14 +- src/solvers/enumpoly/poly.imp | 2 +- src/solvers/nashsupport/efgsupport.cc | 144 ++++ .../nfgensup.h => nashsupport/nashsupport.h} | 32 +- src/solvers/nashsupport/nfgsupport.cc | 114 +++ 20 files changed, 381 insertions(+), 1056 deletions(-) delete mode 100644 src/solvers/enumpoly/efgensup.cc delete mode 100644 src/solvers/enumpoly/efgensup.h delete mode 100644 src/solvers/enumpoly/nfgensup.cc create mode 100644 src/solvers/nashsupport/efgsupport.cc rename src/solvers/{enumpoly/nfgensup.h => nashsupport/nashsupport.h} (55%) create mode 100644 src/solvers/nashsupport/nfgsupport.cc diff --git a/Makefile.am b/Makefile.am index d1d20f610..0745f55c5 100644 --- a/Makefile.am +++ b/Makefile.am @@ -413,8 +413,14 @@ gambit_enummixed_SOURCES = \ src/solvers/enummixed/enummixed.h \ src/tools/enummixed/enummixed.cc +gambit_nashsupport_SOURCES = \ + src/solvers/nashsupport/efgsupport.cc \ + src/solvers/nashsupport/nfgsupport.cc \ + src/solvers/nashsupport/nashsupport.h + +# For enumpoly, sources starting in 'pel' are from Pelican. gambit_enumpoly_SOURCES = \ - ${core_SOURCES} ${game_SOURCES} \ + ${core_SOURCES} ${game_SOURCES} ${gambit_nashsupport_SOURCES} \ src/solvers/enumpoly/gpartltr.cc \ src/solvers/enumpoly/gpartltr.h \ src/solvers/enumpoly/gpartltr.imp \ @@ -457,16 +463,12 @@ gambit_enumpoly_SOURCES = \ 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 \ diff --git a/setup.py b/setup.py index e69a70839..a3d66b661 100644 --- a/setup.py +++ b/setup.py @@ -88,6 +88,7 @@ def solver_library_config(library_name: str, paths: list) -> tuple: 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( @@ -143,7 +144,7 @@ def readme(): "deprecated", ], libraries=[cppgambit_bimatrix, cppgambit_liap, cppgambit_logit, cppgambit_simpdiv, - cppgambit_gtracer, + cppgambit_gtracer, cppgambit_enumpoly, cppgambit_games, cppgambit_core, lrslib], package_dir={"": "src"}, packages=["pygambit"], diff --git a/src/games/behavspt.cc b/src/games/behavspt.cc index 0030f74d3..7672ee114 100644 --- a/src/games/behavspt.cc +++ b/src/games/behavspt.cc @@ -130,15 +130,6 @@ bool BehaviorSupportProfile::RemoveAction(const GameAction &s) } } -bool BehaviorSupportProfile::RemoveAction(const GameAction &s, List &list) -{ - for (const auto &node : GetMembers(s->GetInfoset())) { - DeactivateSubtree(node->GetChild(s->GetNumber()), list); - } - // the following returns false if s was not in the support - return RemoveAction(s); -} - void BehaviorSupportProfile::AddAction(const GameAction &s) { GameInfoset infoset = s->GetInfoset(); diff --git a/src/games/behavspt.h b/src/games/behavspt.h index 8442ad4b4..a9329590d 100644 --- a/src/games/behavspt.h +++ b/src/games/behavspt.h @@ -107,9 +107,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 diff --git a/src/games/stratspt.cc b/src/games/stratspt.cc index 3d759e2fc..e8acc4cf2 100644 --- a/src/games/stratspt.cc +++ b/src/games/stratspt.cc @@ -394,10 +394,10 @@ bool StrategySupportProfile::Overwhelms(const GameStrategy &s, const GameStrateg } //=========================================================================== -// class StrategySupportProfile::iterator +// class StrategySupportProfile::const_iterator //=========================================================================== -bool StrategySupportProfile::iterator::GoToNext() +bool StrategySupportProfile::const_iterator::GoToNext() { if (strat != support.NumStrategies(pl)) { strat++; @@ -409,11 +409,14 @@ bool StrategySupportProfile::iterator::GoToNext() return true; } else { + // The "at end" iterator points to (NumPlayers() + 1, 1) + pl++; + strat = 1; return false; } } -bool StrategySupportProfile::iterator::IsSubsequentTo(const GameStrategy &s) const +bool StrategySupportProfile::const_iterator::IsSubsequentTo(const GameStrategy &s) const { if (pl > s->GetPlayer()->GetNumber()) { return true; diff --git a/src/games/stratspt.h b/src/games/stratspt.h index 70e2268ae..abdf202a2 100644 --- a/src/games/stratspt.h +++ b/src/games/stratspt.h @@ -148,24 +148,24 @@ class StrategySupportProfile { bool Overwhelms(const GameStrategy &s, const GameStrategy &t, bool p_strict) const; //@} - class iterator { + class const_iterator { public: /// @name Lifecycle //@{ - explicit iterator(const StrategySupportProfile &S, int p_pl = 1, int p_st = 1) + explicit const_iterator(const StrategySupportProfile &S, int p_pl = 1, int p_st = 1) : support(S), pl(p_pl), strat(p_st) { } - ~iterator() = default; + ~const_iterator() = default; //@} /// @name Operator overloading //@{ - bool operator==(const iterator &other) const + bool operator==(const const_iterator &other) const { return (support == other.support && pl == other.pl && strat == other.strat); } - bool operator!=(const iterator &other) const { return !(*this == other); } + bool operator!=(const const_iterator &other) const { return !(*this == other); } //@} /// @name Manipulation @@ -173,11 +173,16 @@ class StrategySupportProfile { /// Advance to next strategy; return False when advancing past last strategy. bool GoToNext(); /// Advance to next strategy - void operator++() { GoToNext(); } + const_iterator &operator++() + { + GoToNext(); + return *this; + } //@} /// @name Access to state information //@{ + GameStrategy operator*() const { return GetStrategy(); } GameStrategy GetStrategy() const { return support.GetStrategy(pl, strat); } int StrategyIndex() const { return strat; } GamePlayer GetPlayer() const { return support.GetGame()->GetPlayer(pl); } @@ -195,8 +200,8 @@ class StrategySupportProfile { int pl, strat; }; - iterator begin() const { return iterator(*this); } - iterator end() const { return iterator(*this, m_nfg->NumPlayers() + 1); } + const_iterator begin() const { return const_iterator(*this); } + const_iterator end() const { return const_iterator(*this, m_nfg->NumPlayers() + 1); } }; } // end namespace Gambit diff --git a/src/pygambit/gambit.pxd b/src/pygambit/gambit.pxd index 220fb467f..aa8f0b463 100644 --- a/src/pygambit/gambit.pxd +++ b/src/pygambit/gambit.pxd @@ -1,7 +1,7 @@ from libcpp cimport bool from libcpp.string cimport string -from libcpp.memory cimport shared_ptr -from libcpp.list cimport list as clist +from libcpp.memory cimport shared_ptr, unique_ptr +from libcpp.list cimport list as stdlist cdef extern from "gambit.h": @@ -364,6 +364,12 @@ cdef extern from "util.h": c_Rational to_rational(char *) except + + stdlist[c_StrategySupportProfile *] make_list_of_pointer( + stdlist[c_StrategySupportProfile] + ) 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 + @@ -459,6 +465,13 @@ cdef extern from "solvers/gnm/gnm.h": int p_localNewtonInterval, int p_localNewtonMaxits ) 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/logit/logit.h": cdef cppclass c_LogitQREMixedBehaviorProfile "LogitQREMixedBehaviorProfile": c_LogitQREMixedBehaviorProfile(c_Game) except + @@ -488,8 +501,8 @@ cdef extern from "nash.h": c_List[c_LogitQREMixedBehaviorProfile] LogitBehaviorPrincipalBranchWrapper( c_Game, double, double, double ) except + - clist[shared_ptr[c_LogitQREMixedBehaviorProfile]] LogitBehaviorAtLambdaWrapper( - c_Game, clist[double], double, double + stdlist[shared_ptr[c_LogitQREMixedBehaviorProfile]] LogitBehaviorAtLambdaWrapper( + c_Game, stdlist[double], double, double ) except + shared_ptr[c_LogitQREMixedBehaviorProfile] LogitBehaviorEstimateWrapper( shared_ptr[c_MixedBehaviorProfileDouble], bool, double, double @@ -500,8 +513,8 @@ cdef extern from "nash.h": c_List[c_LogitQREMixedStrategyProfile] LogitStrategyPrincipalBranchWrapper( c_Game, double, double, double ) except + - clist[shared_ptr[c_LogitQREMixedStrategyProfile]] LogitStrategyAtLambdaWrapper( - c_Game, clist[double], double, double + stdlist[shared_ptr[c_LogitQREMixedStrategyProfile]] LogitStrategyAtLambdaWrapper( + c_Game, stdlist[double], double, double ) except + shared_ptr[c_LogitQREMixedStrategyProfile] LogitStrategyEstimateWrapper( shared_ptr[c_MixedStrategyProfileDouble], bool, double, double diff --git a/src/pygambit/nash.pxi b/src/pygambit/nash.pxi index 15225aef3..6c1edd43f 100644 --- a/src/pygambit/nash.pxi +++ b/src/pygambit/nash.pxi @@ -182,6 +182,16 @@ def _gnm_strategy_solve( raise +def _nashsupport_strategy_solve(game: Game) -> typing.List[StrategySupportProfile]: + ret = [] + result = PossibleNashStrategySupports(game.game) + for c_support in make_list_of_pointer(deref(result).m_supports): + support = StrategySupportProfile(list(game.strategies), game) + support.support.reset(c_support) + ret.append(support) + return ret + + def _logit_strategy_solve( game: Game, maxregret: float, first_step: float, max_accel: float, ) -> typing.List[MixedStrategyProfileDouble]: diff --git a/src/pygambit/nash.py b/src/pygambit/nash.py index 712d46eb2..530dffba6 100644 --- a/src/pygambit/nash.py +++ b/src/pygambit/nash.py @@ -527,6 +527,27 @@ def gnm_solve( raise +def possible_nash_supports(game: libgbt.Game) -> typing.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 logit_solve( game: libgbt.Game, use_strategic: bool = False, diff --git a/src/pygambit/util.h b/src/pygambit/util.h index 1ed1d52cb..43acb579c 100644 --- a/src/pygambit/util.h +++ b/src/pygambit/util.h @@ -107,3 +107,12 @@ inline Rational to_rational(const char *p_value) { return lexical_cast(std::string(p_value)); } + +template std::list make_list_of_pointer(const std::list &p_list) +{ + std::list result; + for (auto element : p_list) { + result.push_back(new T(element)); + } + return result; +} diff --git a/src/solvers/enumpoly/efgensup.cc b/src/solvers/enumpoly/efgensup.cc deleted file mode 100644 index 60858e110..000000000 --- a/src/solvers/enumpoly/efgensup.cc +++ /dev/null @@ -1,678 +0,0 @@ -// -// This file is part of Gambit -// Copyright (c) 1994-2024, The Gambit Project (http://www.gambit-project.org) -// -// FILE: src/tools/enumpoly/efgensup.cc -// Enumerate undominated subsupports -// -// 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 -#include "efgensup.h" - -// We build a series of functions of increasing complexity. The -// final one, which is our goal, is the undominated support function. -// We begin by simply enumerating all subsupports. - -void AllSubsupportsRECURSIVE(const Gambit::BehaviorSupportProfile &s, - Gambit::BehaviorSupportProfile *sact, ActionCursorForSupport *c, - Gambit::List &list) -{ - list.push_back(*sact); - - ActionCursorForSupport c_copy(*c); - - do { - if (sact->Contains(c_copy.GetAction())) { - sact->RemoveAction(c_copy.GetAction()); - AllSubsupportsRECURSIVE(s, sact, &c_copy, list); - sact->AddAction(c_copy.GetAction()); - } - } while (c_copy.GoToNext()); -} - -Gambit::List -AllSubsupports(const Gambit::BehaviorSupportProfile &S) -{ - Gambit::List answer; - Gambit::BehaviorSupportProfile SAct(S); - ActionCursorForSupport cursor(S); - - AllSubsupportsRECURSIVE(S, &SAct, &cursor, answer); - - return answer; -} - -bool HasActiveActionsAtActiveInfosets(const Gambit::BehaviorSupportProfile &p_support) -{ - for (auto player : p_support.GetGame()->GetPlayers()) { - for (auto infoset : player->GetInfosets()) { - if (p_support.IsReachable(infoset) && !p_support.HasAction(infoset)) { - return false; - } - } - } - return true; -} - -bool HasActiveActionsAtActiveInfosetsAndNoOthers(const Gambit::BehaviorSupportProfile &p_support) -{ - for (auto player : p_support.GetGame()->GetPlayers()) { - for (auto infoset : player->GetInfosets()) { - if (p_support.IsReachable(infoset) != p_support.HasAction(infoset)) { - return false; - } - } - } - return true; -} - -// Subsupports of a given support are _path equivalent_ if they -// agree on every infoset that can be reached under either, hence both, -// of them. The next routine outputs one support for each equivalence -// class. It is not for use in solution routines, but is instead a -// prototype of the eventual path enumerator, which will also perform -// dominance elimination. - -void AllInequivalentSubsupportsRECURSIVE(const Gambit::BehaviorSupportProfile &s, - Gambit::BehaviorSupportProfile *sact, - ActionCursorForSupport *c, - Gambit::List &list) -{ - if (HasActiveActionsAtActiveInfosetsAndNoOthers(*sact)) { - list.push_back(*sact); - } - - ActionCursorForSupport c_copy(*c); - - do { - if (sact->Contains(c_copy.GetAction())) { - - Gambit::List deactivated_infosets; - sact->RemoveAction(c_copy.GetAction(), deactivated_infosets); - - if (!c_copy.DeletionsViolateActiveCommitments(sact, &deactivated_infosets)) { - AllInequivalentSubsupportsRECURSIVE(s, sact, &c_copy, list); - } - sact->AddAction(c_copy.GetAction()); - } - } while (c_copy.GoToNext()); -} - -Gambit::List -AllInequivalentSubsupports(const Gambit::BehaviorSupportProfile &S) -{ - Gambit::List answer; - Gambit::BehaviorSupportProfile SAct(S); - ActionCursorForSupport cursor(S); - - AllInequivalentSubsupportsRECURSIVE(S, &SAct, &cursor, answer); - - return answer; -} - -void AllUndominatedSubsupportsRECURSIVE(const Gambit::BehaviorSupportProfile &s, - Gambit::BehaviorSupportProfile *sact, - ActionCursorForSupport *c, bool strong, bool conditional, - Gambit::List &list) -{ - bool abort = false; - bool no_deletions = true; - bool check_domination = false; - if (HasActiveActionsAtActiveInfosets(*sact)) { - check_domination = true; - } - Gambit::List deletion_list; - ActionCursorForSupport scanner(s); - - // First we collect all the actions that can be deleted. - do { - Gambit::GameAction this_action = scanner.GetAction(); - bool delete_this_action = false; - - if (sact->Contains(this_action)) { - if (!sact->IsReachable(this_action->GetInfoset())) { - delete_this_action = true; - } - else { - if (check_domination) { - if (sact->IsDominated(this_action, strong, conditional)) { - delete_this_action = true; - } - } - } - } - - if (delete_this_action) { - no_deletions = false; - if (c->IsSubsequentTo(this_action)) { - abort = true; - } - else { - deletion_list.push_back(this_action); - } - } - } while (!abort && scanner.GoToNext()); - - // Now we delete them, recurse, then restore - if (!abort && !no_deletions) { - Gambit::List actual_deletions; - for (int i = 1; !abort && i <= deletion_list.Length(); i++) { - actual_deletions.push_back(deletion_list[i]); - Gambit::List deactivated_infosets; - - sact->RemoveAction(deletion_list[i], deactivated_infosets); - - if (c->DeletionsViolateActiveCommitments(sact, &deactivated_infosets)) { - abort = true; - } - } - - if (!abort) { - AllUndominatedSubsupportsRECURSIVE(s, sact, c, strong, conditional, list); - } - - for (int i = 1; i <= actual_deletions.Length(); i++) { - sact->AddAction(actual_deletions[i]); - } - } - - // If there are no deletions, we ask if it is consistent, then recurse. - if (!abort && no_deletions) { - if (HasActiveActionsAtActiveInfosetsAndNoOthers(*sact)) { - list.push_back(*sact); - } - - ActionCursorForSupport c_copy(*c); - - do { - if (sact->Contains(c_copy.GetAction())) { - - Gambit::List deactivated_infosets; - sact->RemoveAction(c_copy.GetAction(), deactivated_infosets); - - if (!c_copy.DeletionsViolateActiveCommitments(sact, &deactivated_infosets)) { - AllUndominatedSubsupportsRECURSIVE(s, sact, &c_copy, strong, conditional, list); - } - sact->AddAction(c_copy.GetAction()); - } - } while (c_copy.GoToNext()); - } -} - -Gambit::List -AllUndominatedSubsupports(const Gambit::BehaviorSupportProfile &S, bool strong, bool conditional) -{ - Gambit::List answer; - Gambit::BehaviorSupportProfile sact(S); - ActionCursorForSupport cursor(S); - - AllUndominatedSubsupportsRECURSIVE(S, &sact, &cursor, strong, conditional, answer); - - return answer; -} - -void PossibleNashSubsupportsRECURSIVE(const Gambit::BehaviorSupportProfile &s, - Gambit::BehaviorSupportProfile *sact, - ActionCursorForSupport *c, - Gambit::List &list) -{ - bool abort = false; - bool add_support = true; - - bool check_domination = false; - if (HasActiveActionsAtActiveInfosets(*sact)) { - check_domination = true; - } - Gambit::List deletion_list; - ActionCursorForSupport scanner(s); - - do { - Gambit::GameAction this_action = scanner.GetAction(); - bool delete_this_action = false; - - if (sact->Contains(this_action)) { - if (!sact->IsReachable(this_action->GetInfoset())) { - delete_this_action = true; - } - else { - if (check_domination) { - if (sact->IsDominated(this_action, true, true) || - sact->IsDominated(this_action, true, false)) { - add_support = false; - if (c->InfosetGuaranteedActiveByPriorCommitments(sact, this_action->GetInfoset())) { - delete_this_action = true; - } - } - } - } - } - - if (delete_this_action) { - if (c->IsSubsequentTo(this_action)) { - abort = true; - } - else { - deletion_list.push_back(this_action); - } - } - } while (!abort && scanner.GoToNext()); - - std::cout << "length of deletion list = " << deletion_list.Length() << std::endl; - if (!abort) { - Gambit::List actual_deletions; - for (int i = 1; !abort && i <= deletion_list.Length(); i++) { - Gambit::List deactivated_infosets; - sact->RemoveAction(deletion_list[i], deactivated_infosets); - if (c->DeletionsViolateActiveCommitments(sact, &deactivated_infosets)) { - abort = true; - } - actual_deletions.push_back(deletion_list[i]); - } - - if (!abort && deletion_list.Length() > 0) { - PossibleNashSubsupportsRECURSIVE(s, sact, c, list); - } - - for (int i = 1; i <= actual_deletions.Length(); i++) { - sact->AddAction(actual_deletions[i]); - } - } - - if (!abort && deletion_list.Length() == 0) { - - if (add_support && HasActiveActionsAtActiveInfosetsAndNoOthers(*sact)) { - list.push_back(*sact); - } - ActionCursorForSupport c_copy(*c); - do { - if (sact->Contains(c_copy.GetAction())) { - Gambit::List deactivated_infosets; - sact->RemoveAction(c_copy.GetAction(), deactivated_infosets); - if (!c_copy.DeletionsViolateActiveCommitments(sact, &deactivated_infosets)) { - PossibleNashSubsupportsRECURSIVE(s, sact, &c_copy, list); - } - - sact->AddAction(c_copy.GetAction()); - } - } while (c_copy.GoToNext()); - } -} - -Gambit::List -SortSupportsBySize(Gambit::List &list) -{ - Gambit::Array sizes(list.Length()); - for (int i = 1; i <= list.Length(); i++) { - sizes[i] = list[i].BehaviorProfileLength(); - } - - Gambit::Array listproxy(list.Length()); - for (int i = 1; i <= list.Length(); i++) { - listproxy[i] = i; - } - - int maxsize(0); - for (int i = 1; i <= list.Length(); i++) { - if (sizes[i] > maxsize) { - maxsize = sizes[i]; - } - } - - int cursor(1); - - for (int j = 0; j < maxsize; j++) { - int scanner(list.Length()); - while (cursor < scanner) { - if (sizes[scanner] != j) { - scanner--; - } - else { - while (sizes[cursor] == j) { - cursor++; - } - if (cursor < scanner) { - int tempindex = listproxy[cursor]; - listproxy[cursor] = listproxy[scanner]; - listproxy[scanner] = tempindex; - int tempsize = sizes[cursor]; - sizes[cursor] = sizes[scanner]; - sizes[scanner] = tempsize; - cursor++; - } - } - } - } - - Gambit::List answer; - for (int i = 1; i <= list.Length(); i++) { - answer.push_back(list[listproxy[i]]); - } - - return answer; -} - -namespace { // to keep the recursive function private - -using namespace Gambit; - -#ifdef UNUSED -void PrintSupport(std::ostream &p_stream, const std::string &p_label, - const Gambit::BehaviorSupportProfile &p_support) -{ - p_stream << p_label; - - for (int pl = 1; pl <= p_support.GetGame()->NumPlayers(); pl++) { - GamePlayer player = p_support.GetGame()->GetPlayer(pl); - - for (int iset = 1; iset <= player->NumInfosets(); iset++) { - GameInfoset infoset = player->GetInfoset(iset); - - p_stream << ","; - - for (int act = 1; act <= infoset->NumActions(); act++) { - if (p_support.Contains(infoset->GetAction(act))) { - p_stream << "1"; - } - else { - p_stream << "0"; - } - } - } - } - p_stream << std::endl; -} -#endif // UNUSED - -void PossibleNashSubsupports(const Gambit::BehaviorSupportProfile &p_support, - const ActionCursorForSupport &p_cursor, - Gambit::List &p_list) -{ - ActionCursorForSupport copy(p_cursor); - if (!copy.GoToNext()) { - if (HasActiveActionsAtActiveInfosetsAndNoOthers(p_support)) { - p_list.push_back(p_support); - } - - Gambit::BehaviorSupportProfile copySupport(p_support); - copySupport.RemoveAction(p_cursor.GetAction()); - if (HasActiveActionsAtActiveInfosetsAndNoOthers(copySupport)) { - p_list.push_back(copySupport); - } - return; - } - PossibleNashSubsupports(p_support, copy, p_list); - - Gambit::BehaviorSupportProfile copySupport(p_support); - copySupport.RemoveAction(p_cursor.GetAction()); - PossibleNashSubsupports(copySupport, copy, p_list); -} - -} // namespace - -// -// This is a naive version of the real thing. The original one -// (see below, commented out) no longer works after underlying API -// changes. I suspect the problem is that the behavior support class -// was hacked to make this work, and recent refactorings have undone -// the hacks it depended on. So, for now, we do a really simple -// implementation that works (but may be inefficient), and we will -// go from there later. -// -Gambit::List -PossibleNashSubsupports(const Gambit::BehaviorSupportProfile &p_support) -{ - ActionCursorForSupport cursor(p_support); - Gambit::List supports; - - PossibleNashSubsupports(p_support, cursor, supports); - return supports; -} - -/* -Gambit::List PossibleNashSubsupports(const -Gambit::BehaviorSupportProfile &S) -{ - Gambit::List answer; - Gambit::BehaviorSupportProfile sact(S); - ActionCursorForSupport cursor(S); - - PossibleNashSubsupportsRECURSIVE(S, &sact, &cursor, answer); - - // At this point answer has all consistent subsupports without - // any strong dominations. We now edit the list, removing all - // subsupports that exhibit weak dominations, and we also eliminate - // subsupports exhibiting domination by currently inactive actions. - - for (int i = answer.Length(); i >= 1; i--) { - Gambit::BehaviorSupportProfile current(answer[i]); - ActionCursorForSupport crsr(S); - bool remove = false; - do { - Gambit::GameAction act = crsr.GetAction(); - if (current.Contains(act)) - for (int j = 1; j <= act->GetInfoset()->NumActions(); j++) { - Gambit::GameAction other_act = act->GetInfoset()->GetAction(j); - if (other_act != act) - if (current.Contains(other_act)) { - if (current.Dominates(other_act,act,false,true) || - current.Dominates(other_act,act,false,false)) - remove = true; - } - else { - current.AddAction(other_act); - if (current.HasActiveActionsAtActiveInfosetsAndNoOthers()) - if (current.Dominates(other_act,act,false,true) || - current.Dominates(other_act,act,false,false)) { - remove = true; - } - current.RemoveAction(other_act); - } - } - } while (crsr.GoToNext() && !remove); - if (remove) - answer.Remove(i); - } - - return SortSupportsBySize(answer); -} -*/ - -//---------------------------------------------------- -// ActionCursorForSupport -// --------------------------------------------------- - -ActionCursorForSupport::ActionCursorForSupport(const Gambit::BehaviorSupportProfile &S) - : support(S), pl(1), iset(1), act(1) -{ - Gambit::Game efg = S.GetGame(); - - // Make sure that (pl,iset) points to a valid information set: - // when solving by subgames, some players may not have any information - // sets at all. - for (pl = 1; pl <= efg->NumPlayers(); pl++) { - if (efg->GetPlayer(pl)->NumInfosets() > 0) { - for (iset = 1; iset <= efg->GetPlayer(pl)->NumInfosets(); iset++) { - if (S.NumActions(pl, iset) > 0) { - return; - } - } - } - } -} - -ActionCursorForSupport::ActionCursorForSupport(const ActionCursorForSupport &ac) - - = default; - -ActionCursorForSupport::~ActionCursorForSupport() = default; - -ActionCursorForSupport &ActionCursorForSupport::operator=(const ActionCursorForSupport &rhs) -{ - if (this != &rhs) { - support = rhs.support; - pl = rhs.pl; - iset = rhs.iset; - act = rhs.act; - } - return *this; -} - -bool ActionCursorForSupport::operator==(const ActionCursorForSupport &rhs) const -{ - if (support != rhs.support || pl != rhs.pl || iset != rhs.iset || act != rhs.act) { - return false; - } - return true; -} - -bool ActionCursorForSupport::operator!=(const ActionCursorForSupport &rhs) const -{ - return (!(*this == rhs)); -} - -bool ActionCursorForSupport::GoToNext() -{ - if (act != support.NumActions(pl, iset)) { - act++; - return true; - } - - int temppl(pl); - int tempiset(iset); - tempiset++; - - while (temppl <= support.GetGame()->NumPlayers()) { - while (tempiset <= support.GetGame()->GetPlayer(temppl)->NumInfosets()) { - if (support.NumActions(temppl, tempiset) > 0) { - pl = temppl; - iset = tempiset; - act = 1; - return true; - } - else { - tempiset++; - } - } - tempiset = 1; - temppl++; - } - return false; -} - -Gambit::GameAction ActionCursorForSupport::GetAction() const -{ - return support.GetAction(pl, iset, act); -} - -int ActionCursorForSupport::ActionIndex() const { return act; } - -Gambit::GameInfoset ActionCursorForSupport::GetInfoset() const -{ - return support.GetGame()->GetPlayer(pl)->GetInfoset(iset); -} - -int ActionCursorForSupport::InfosetIndex() const { return iset; } - -Gambit::GamePlayer ActionCursorForSupport::GetPlayer() const -{ - return support.GetGame()->GetPlayer(pl); -} - -int ActionCursorForSupport::PlayerIndex() const { return pl; } - -bool ActionCursorForSupport::IsLast() const -{ - if (pl == support.GetGame()->NumPlayers()) { - if (iset == support.GetGame()->GetPlayer(pl)->NumInfosets()) { - if (act == support.NumActions(pl, iset)) { - return true; - } - } - } - return false; -} - -bool ActionCursorForSupport::IsSubsequentTo(const Gambit::GameAction &a) const -{ - if (pl > a->GetInfoset()->GetPlayer()->GetNumber()) { - return true; - } - else if (pl < a->GetInfoset()->GetPlayer()->GetNumber()) { - return false; - } - else if (iset > a->GetInfoset()->GetNumber()) { - return true; - } - else if (iset < a->GetInfoset()->GetNumber()) { - return false; - } - else if (act > a->GetNumber()) { - return true; - } - else { - return false; - } -} - -bool ActionCursorForSupport::DeletionsViolateActiveCommitments( - const Gambit::BehaviorSupportProfile *S, const Gambit::List *infosetlist) -{ - for (int i = 1; i <= infosetlist->Length(); i++) { - Gambit::GameInfoset infoset = (*infosetlist)[i]; - if (infoset->GetPlayer()->GetNumber() < PlayerIndex() || - (infoset->GetPlayer()->GetNumber() == PlayerIndex() && - infoset->GetNumber() < InfosetIndex())) { - if (S->NumActions(infoset->GetPlayer()->GetNumber(), infoset->GetNumber()) > 0) { - return true; - } - } - if (infoset->GetPlayer()->GetNumber() == PlayerIndex() && - infoset->GetNumber() == InfosetIndex()) { - for (int action = 1; action < ActionIndex(); action++) { - if (S->Contains(infoset->GetAction(action))) { - return true; - } - } - } - } - return false; -} - -bool ActionCursorForSupport::InfosetGuaranteedActiveByPriorCommitments( - const Gambit::BehaviorSupportProfile *S, const Gambit::GameInfoset &infoset) -{ - Gambit::List members; - for (int i = 1; i <= infoset->NumMembers(); i++) { - members.push_back(infoset->GetMember(i)); - } - - for (int i = 1; i <= members.Length(); i++) { - Gambit::GameNode current = members[i]; - if (current == S->GetGame()->GetRoot()) { - return true; - } - else { - while (S->Contains(current->GetPriorAction()) && IsSubsequentTo(current->GetPriorAction())) { - current = current->GetParent(); - if (current == S->GetGame()->GetRoot()) { - return true; - } - } - } - } - return false; -} diff --git a/src/solvers/enumpoly/efgensup.h b/src/solvers/enumpoly/efgensup.h deleted file mode 100644 index ad315bb53..000000000 --- a/src/solvers/enumpoly/efgensup.h +++ /dev/null @@ -1,130 +0,0 @@ -// -// This file is part of Gambit -// Copyright (c) 1994-2024, The Gambit Project (http://www.gambit-project.org) -// -// FILE: src/tools/enumpoly/efgensup.h -// Enumerate undominated subsupports -// -// 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" - -class ActionCursorForSupport; - -// We build a series of functions of increasing complexity. The -// final one, which is our goal, is the undominated support function. -// We begin by simply enumerating all subsupports. - -void AllSubsupportsRECURSIVE(const Gambit::BehaviorSupportProfile &s, - Gambit::BehaviorSupportProfile *sact, ActionCursorForSupport *c, - Gambit::List &list); - -Gambit::List -AllSubsupports(const Gambit::BehaviorSupportProfile &S); - -// Subsupports of a given support are _path equivalent_ if they -// agree on every infoset that can be reached under either, hence both, -// of them. The next routine outputs one support for each equivalence -// class by outputting only those subsupports with _no_ active -// actions at each unreached infoset. - -void AllInequivalentSubsupportsRECURSIVE(const Gambit::BehaviorSupportProfile &s, - Gambit::BehaviorSupportProfile *sact, - ActionCursorForSupport *c, - Gambit::List &list); - -Gambit::List -AllInequivalentSubsupports(const Gambit::BehaviorSupportProfile &S); - -// The following routines combine to return all supports that do not -// exhibit particular type of domination. This was a prototype for -// PossibleNashSubsupports, and displays the methods used there, -// but it does NOT do exactly what is advertised with respect to -// weak domination. This is because the recursion may eliminate -// an action that is weakly dominated at some stage of the truncation -// process, when, after more truncations, it might be no longer weakly -// dominated, and thus part of an allowed subsupport. - -void AllUndominatedSubsupportsRECURSIVE(const Gambit::BehaviorSupportProfile &s, - Gambit::BehaviorSupportProfile *sact, - ActionCursorForSupport *c, bool strong, bool conditional, - Gambit::List &list); - -Gambit::List -AllUndominatedSubsupports(const Gambit::BehaviorSupportProfile &S, bool strong, bool conditional); - -// The following two routines combine to produce all subsupports that could -// host the path of a behavioral Nash equilibrium. These are subsupports -// that have no action, at an active infoset, that is weakly dominated by -// another active action, either in the conditional sense (for any active -// node in the infoset) or the unconditional sense. In addition we -// check for domination by actions that are inactive, but whose activation -// would not activate any currently inactive infosets, so that the -// subsupport resulting from activation is consistent, in the sense -// of having active actions at all active infosets, and not at other -// infosets. - -void PossibleNashSubsupportsRECURSIVE(const Gambit::BehaviorSupportProfile &s, - Gambit::BehaviorSupportProfile *sact, - ActionCursorForSupport *c, - Gambit::List &list); - -Gambit::List -SortSupportsBySize(Gambit::List &); - -Gambit::List -PossibleNashSubsupports(const Gambit::BehaviorSupportProfile &S); - -///////////////// Utility Cursor Class ///////////////////// - -class ActionCursorForSupport { -protected: - Gambit::BehaviorSupportProfile support; - int pl; - int iset; - int act; - -public: - // Constructors and dtor - explicit ActionCursorForSupport(const Gambit::BehaviorSupportProfile &S); - ActionCursorForSupport(const ActionCursorForSupport &a); - ~ActionCursorForSupport(); - - // Operators - ActionCursorForSupport &operator=(const ActionCursorForSupport &); - bool operator==(const ActionCursorForSupport &) const; - bool operator!=(const ActionCursorForSupport &) const; - - // Manipulation - bool GoToNext(); - - // Information - Gambit::GameAction GetAction() const; - int ActionIndex() const; - Gambit::GameInfoset GetInfoset() const; - int InfosetIndex() const; - Gambit::GamePlayer GetPlayer() const; - int PlayerIndex() const; - - bool IsLast() const; - bool IsSubsequentTo(const Gambit::GameAction &) const; - - // Special - bool InfosetGuaranteedActiveByPriorCommitments(const Gambit::BehaviorSupportProfile *, - const Gambit::GameInfoset &); - bool DeletionsViolateActiveCommitments(const Gambit::BehaviorSupportProfile *, - const Gambit::List *); -}; diff --git a/src/solvers/enumpoly/efgpoly.cc b/src/solvers/enumpoly/efgpoly.cc index f4f1b6cae..610a2f221 100644 --- a/src/solvers/enumpoly/efgpoly.cc +++ b/src/solvers/enumpoly/efgpoly.cc @@ -26,7 +26,7 @@ using namespace Gambit; -#include "efgensup.h" +#include "solvers/nashsupport/nashsupport.h" #include "sfg.h" #include "gpoly.h" #include "gpolylst.h" @@ -377,15 +377,15 @@ void PrintSupport(std::ostream &p_stream, const std::string &p_label, void EnumPolySolveExtensive(const Game &p_game) { - List supports(PossibleNashSubsupports(BehaviorSupportProfile(p_game))); + auto possible_supports = PossibleNashBehaviorSupports(p_game); - for (int i = 1; i <= supports.Length(); i++) { + for (auto support : possible_supports->m_supports) { if (g_verbose) { - PrintSupport(std::cout, "candidate", supports[i]); + PrintSupport(std::cout, "candidate", support); } bool isSingular = false; - List> newsolns = SolveSupport(supports[i], isSingular); + List> newsolns = SolveSupport(support, isSingular); for (int j = 1; j <= newsolns.Length(); j++) { MixedBehaviorProfile fullProfile = ToFullSupport(newsolns[j]); @@ -395,7 +395,7 @@ void EnumPolySolveExtensive(const Game &p_game) } if (isSingular && g_verbose) { - PrintSupport(std::cout, "singular", supports[i]); + PrintSupport(std::cout, "singular", support); } } } diff --git a/src/solvers/enumpoly/nfgcpoly.h b/src/solvers/enumpoly/nfgcpoly.h index 6fc02df3a..fa24ac224 100644 --- a/src/solvers/enumpoly/nfgcpoly.h +++ b/src/solvers/enumpoly/nfgcpoly.h @@ -26,7 +26,7 @@ #define NFGCPOLY_H #include "gambit.h" -#include "nfgensup.h" +#include "solvers/nashsupport/nashsupport.h" #include "gpoly.h" #include "gpolylst.h" #include "rectangl.h" diff --git a/src/solvers/enumpoly/nfgensup.cc b/src/solvers/enumpoly/nfgensup.cc deleted file mode 100644 index e67c7a76e..000000000 --- a/src/solvers/enumpoly/nfgensup.cc +++ /dev/null @@ -1,187 +0,0 @@ -// -// This file is part of Gambit -// Copyright (c) 1994-2024, The Gambit Project (http://www.gambit-project.org) -// -// FILE: src/tools/enumpoly/nfgensup.cc -// Enumerate undominated subsupports of a support -// -// 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 "nfgensup.h" - -using namespace Gambit; - -namespace { - -List SortSupportsBySize(List &p_list) -{ - Array sizes(p_list.Length()); - for (int i = 1; i <= p_list.Length(); i++) { - sizes[i] = p_list[i].MixedProfileLength(); - } - - Array listproxy(p_list.Length()); - for (int i = 1; i <= p_list.Length(); i++) { - listproxy[i] = i; - } - - int maxsize = 0; - for (int i = 1; i <= p_list.Length(); i++) { - if (sizes[i] > maxsize) { - maxsize = sizes[i]; - } - } - - int cursor = 1; - - for (int j = 0; j < maxsize; j++) { - int scanner(p_list.Length()); - while (cursor < scanner) { - if (sizes[scanner] != j) { - scanner--; - } - else { - while (sizes[cursor] == j) { - cursor++; - } - if (cursor < scanner) { - int tempindex = listproxy[cursor]; - listproxy[cursor] = listproxy[scanner]; - listproxy[scanner] = tempindex; - int tempsize = sizes[cursor]; - sizes[cursor] = sizes[scanner]; - sizes[scanner] = tempsize; - cursor++; - } - } - } - } - - List answer; - for (int i = 1; i <= p_list.Length(); i++) { - answer.push_back(p_list[listproxy[i]]); - } - - return answer; -} - -void PossibleNashSubsupports(const StrategySupportProfile &s, StrategySupportProfile &sact, - StrategySupportProfile::iterator &c, - List &p_list) -{ - bool abort = false; - bool no_deletions = true; - - List deletion_list; - StrategySupportProfile::iterator scanner(s); - - do { - GameStrategy this_strategy = scanner.GetStrategy(); - bool delete_this_strategy = false; - if (sact.Contains(this_strategy)) { - if (sact.IsDominated(this_strategy, true)) { - delete_this_strategy = true; - } - } - if (delete_this_strategy) { - no_deletions = false; - if (c.IsSubsequentTo(this_strategy)) { - abort = true; - } - else { - deletion_list.push_back(this_strategy); - } - } - } while (!abort && scanner.GoToNext()); - - if (!abort) { - List actual_deletions; - for (int i = 1; !abort && i <= deletion_list.Length(); i++) { - actual_deletions.push_back(deletion_list[i]); - sact.RemoveStrategy(deletion_list[i]); - } - - if (!abort && deletion_list.Length() > 0) { - PossibleNashSubsupports(s, sact, c, p_list); - } - - for (int i = 1; i <= actual_deletions.Length(); i++) { - sact.AddStrategy(actual_deletions[i]); - } - } - if (!abort && no_deletions) { - p_list.push_back(sact); - - StrategySupportProfile::iterator c_copy(c); - do { - GameStrategy str_ptr = c_copy.GetStrategy(); - if (sact.Contains(str_ptr) && sact.NumStrategies(str_ptr->GetPlayer()->GetNumber()) > 1) { - sact.RemoveStrategy(str_ptr); - PossibleNashSubsupports(s, sact, c_copy, p_list); - sact.AddStrategy(str_ptr); - } - } while (c_copy.GoToNext()); - } -} - -} // end anonymous namespace - -List PossibleNashSubsupports(const StrategySupportProfile &S) -{ - List answer; - StrategySupportProfile sact(S); - StrategySupportProfile::iterator cursor(S); - PossibleNashSubsupports(S, sact, cursor, answer); - - // At this point answer has all consistent subsupports without - // any strong dominations. We now edit the list, removing all - // subsupports that exhibit weak dominations, and we also eliminate - // subsupports exhibiting domination by currently inactive strategies. - - for (int i = answer.Length(); i >= 1; i--) { - StrategySupportProfile current(answer[i]); - StrategySupportProfile::iterator crsr(S); - bool remove = false; - do { - GameStrategy strat = crsr.GetStrategy(); - if (current.Contains(strat)) { - for (int j = 1; j <= strat->GetPlayer()->NumStrategies(); j++) { - GameStrategy other_strat = strat->GetPlayer()->GetStrategy(j); - if (other_strat != strat) { - if (current.Contains(other_strat)) { - if (current.Dominates(other_strat, strat, false)) { - remove = true; - } - } - else { - current.AddStrategy(other_strat); - if (current.Dominates(other_strat, strat, false)) { - remove = true; - } - current.RemoveStrategy(other_strat); - } - } - } - } - } while (crsr.GoToNext() && !remove); - if (remove) { - answer.Remove(i); - } - } - - return SortSupportsBySize(answer); - return answer; -} diff --git a/src/solvers/enumpoly/nfgpoly.cc b/src/solvers/enumpoly/nfgpoly.cc index 62d9d6ace..796c8f478 100644 --- a/src/solvers/enumpoly/nfgpoly.cc +++ b/src/solvers/enumpoly/nfgpoly.cc @@ -24,7 +24,7 @@ #include #include -#include "nfgensup.h" +#include "solvers/nashsupport/nashsupport.h" #include "gpoly.h" #include "gpolylst.h" #include "rectangl.h" @@ -476,20 +476,20 @@ void PrintSupport(std::ostream &p_stream, const std::string &p_label, void EnumPolySolveStrategic(const Gambit::Game &p_nfg) { - Gambit::List supports = PossibleNashSubsupports(p_nfg); + auto possible_supports = PossibleNashStrategySupports(p_nfg); - for (int i = 1; i <= supports.Length(); i++) { + for (auto support : possible_supports->m_supports) { long newevals = 0; double newtime = 0.0; Gambit::List> newsolns; bool is_singular = false; if (g_verbose) { - PrintSupport(std::cout, "candidate", supports[i]); + PrintSupport(std::cout, "candidate", support); } - PolEnum(supports[i], newsolns, newevals, newtime, is_singular); - + PolEnum(support, newsolns, newevals, newtime, is_singular); + for (int j = 1; j <= newsolns.Length(); j++) { Gambit::MixedStrategyProfile fullProfile = ToFullSupport(newsolns[j]); if (fullProfile.GetLiapValue() < 1.0e-6) { @@ -498,7 +498,7 @@ void EnumPolySolveStrategic(const Gambit::Game &p_nfg) } if (is_singular && g_verbose) { - PrintSupport(std::cout, "singular", supports[i]); + PrintSupport(std::cout, "singular", support); } } } diff --git a/src/solvers/enumpoly/poly.imp b/src/solvers/enumpoly/poly.imp index fa14185d2..4205af4ad 100644 --- a/src/solvers/enumpoly/poly.imp +++ b/src/solvers/enumpoly/poly.imp @@ -307,7 +307,7 @@ template T polynomial::LeadingCoefficient() const } } -template __attribute__((unused)) Gambit::List polynomial::CoefficientList() const +template Gambit::List polynomial::CoefficientList() const { return coeflist; } diff --git a/src/solvers/nashsupport/efgsupport.cc b/src/solvers/nashsupport/efgsupport.cc new file mode 100644 index 000000000..273722788 --- /dev/null +++ b/src/solvers/nashsupport/efgsupport.cc @@ -0,0 +1,144 @@ +// +// This file is part of Gambit +// Copyright (c) 1994-2023, The Gambit Project (http://www.gambit-project.org) +// +// FILE: src/tools/enumpoly/efgensup.cc +// Enumerate undominated subsupports +// +// 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 "nashsupport.h" + +namespace { // to keep the recursive function private + +using namespace Gambit; + +class ActionCursor { +protected: + BehaviorSupportProfile m_support; + int pl{1}, iset{1}, act{1}; + +public: + // Lifecycle + explicit ActionCursor(const BehaviorSupportProfile &S); + ~ActionCursor() = default; + + // Operators + ActionCursor &operator=(const ActionCursor &) = default; + + // Manipulation + bool GoToNext(); + + // State + GameAction GetAction() const { return m_support.GetAction(pl, iset, act); } +}; + +ActionCursor::ActionCursor(const BehaviorSupportProfile &p_support) : m_support(p_support) +{ + Game efg = p_support.GetGame(); + + // Make sure that (pl, iset) points to a valid information set. + // It is permitted to have a game where a player has no information sets. + for (; pl <= efg->NumPlayers(); pl++) { + if (efg->GetPlayer(pl)->NumInfosets() > 0) { + for (iset = 1; iset <= efg->GetPlayer(pl)->NumInfosets(); iset++) { + if (m_support.NumActions(pl, iset) > 0) { + return; + } + } + } + } +} + +bool ActionCursor::GoToNext() +{ + if (act != m_support.NumActions(pl, iset)) { + act++; + return true; + } + + int temppl = pl, tempiset = iset + 1; + while (temppl <= m_support.GetGame()->NumPlayers()) { + while (tempiset <= m_support.GetGame()->GetPlayer(temppl)->NumInfosets()) { + if (m_support.NumActions(temppl, tempiset) > 0) { + pl = temppl; + iset = tempiset; + act = 1; + return true; + } + else { + tempiset++; + } + } + tempiset = 1; + temppl++; + } + return false; +} + +bool HasActiveActionsAtActiveInfosetsAndNoOthers(const BehaviorSupportProfile &p_support) +{ + for (auto player : p_support.GetGame()->GetPlayers()) { + for (auto infoset : player->GetInfosets()) { + if (p_support.IsReachable(infoset) != p_support.HasAction(infoset)) { + return false; + } + } + } + return true; +} + +void PossibleNashBehaviorSupports(const BehaviorSupportProfile &p_support, + const ActionCursor &p_cursor, + std::list &p_list) +{ + ActionCursor copy(p_cursor); + if (!copy.GoToNext()) { + if (HasActiveActionsAtActiveInfosetsAndNoOthers(p_support)) { + p_list.push_back(p_support); + } + + BehaviorSupportProfile copySupport(p_support); + copySupport.RemoveAction(p_cursor.GetAction()); + if (HasActiveActionsAtActiveInfosetsAndNoOthers(copySupport)) { + p_list.push_back(copySupport); + } + return; + } + PossibleNashBehaviorSupports(p_support, copy, p_list); + + BehaviorSupportProfile copySupport(p_support); + copySupport.RemoveAction(p_cursor.GetAction()); + PossibleNashBehaviorSupports(copySupport, copy, p_list); +} + +} // namespace + +// +// TODO: This is a naive implementation that does not take into account +// that removing actions from a support profile can (and often does) lead +// to information sets becoming unreachable. +// +std::shared_ptr +PossibleNashBehaviorSupports(const Game &p_game) +{ + BehaviorSupportProfile support(p_game); + ActionCursor cursor(support); + auto result = std::make_shared(); + + PossibleNashBehaviorSupports(support, cursor, result->m_supports); + return result; +} diff --git a/src/solvers/enumpoly/nfgensup.h b/src/solvers/nashsupport/nashsupport.h similarity index 55% rename from src/solvers/enumpoly/nfgensup.h rename to src/solvers/nashsupport/nashsupport.h index 11e41d635..60ddcb24e 100644 --- a/src/solvers/enumpoly/nfgensup.h +++ b/src/solvers/nashsupport/nashsupport.h @@ -20,18 +20,28 @@ // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // +#ifndef GAMBIT_SOLVERS_NASHSUPPORT_NASHSUPPORT_H +#define GAMBIT_SOLVERS_NASHSUPPORT_NASHSUPPORT_H + #include "gambit.h" using namespace Gambit; -// Produce all subsupports that could -// host the path of a behavioral Nash equilibrium. These are subsupports -// that have no strategy, at an active infoset, that is weakly dominated by -// another active strategy, either in the conditional sense (for any active -// node in the infoset) or the unconditional sense. In addition we -// check for domination by strategys that are inactive, but whose activation -// would not activate any currently inactive infosets, so that the -// subsupport resulting from activation is consistent, in the sense -// of having active strategys at all active infosets, and not at other -// infosets. -List PossibleNashSubsupports(const StrategySupportProfile &S); +class PossibleNashStrategySupportsResult { +public: + std::list m_supports; +}; +// Compute the set of strategy support profiles which can be the support of +// a totally-mixed Nash equilibrium. +std::shared_ptr PossibleNashStrategySupports(const Game &); + +class PossibleNashBehaviorSupportsResult { +public: + std::list m_supports; +}; + +// Compute the set of behavior support profiles which can be the support of +// a totally-mixed Nash equilibrium. +std::shared_ptr PossibleNashBehaviorSupports(const Game &); + +#endif // GAMBIT_SOLVERS_NASHSUPPORT_NASHSUPPORT_H diff --git a/src/solvers/nashsupport/nfgsupport.cc b/src/solvers/nashsupport/nfgsupport.cc new file mode 100644 index 000000000..ac8c59420 --- /dev/null +++ b/src/solvers/nashsupport/nfgsupport.cc @@ -0,0 +1,114 @@ +// +// This file is part of Gambit +// Copyright (c) 1994-2023, The Gambit Project (http://www.gambit-project.org) +// +// FILE: src/tools/enumpoly/nfgensup.cc +// Enumerate undominated subsupports of a support +// +// 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 "nashsupport.h" + +using namespace Gambit; + +namespace { + +// Given a candidate support profile p_candidate, check whether this +// can be the support of a totally mixed Nash equilibrium, by +// checking for weak dominations, and dominations by strategies +// that are in the game but outside the support. +bool AcceptCandidate(const StrategySupportProfile &p_support, + const StrategySupportProfile &p_candidate) +{ + StrategySupportProfile current(p_candidate); + StrategySupportProfile::const_iterator cursor(p_support); + do { + GameStrategy strat = cursor.GetStrategy(); + if (!current.Contains(strat)) { + continue; + } + for (int j = 1; j <= strat->GetPlayer()->NumStrategies(); j++) { + GameStrategy other_strat = strat->GetPlayer()->GetStrategy(j); + if (other_strat != strat) { + if (current.Contains(other_strat) && current.Dominates(other_strat, strat, false)) { + return false; + } + current.AddStrategy(other_strat); + if (current.Dominates(other_strat, strat, false)) { + return false; + } + current.RemoveStrategy(other_strat); + } + } + } while (cursor.GoToNext()); + return true; +} + +void PossibleNashSupports(const StrategySupportProfile &p_support, + const StrategySupportProfile &p_current, + const StrategySupportProfile::const_iterator &cursor, + std::list &p_result) +{ + std::list deletion_list; + for (auto strategy : p_current) { + if (p_current.IsDominated(strategy, true)) { + if (cursor.IsSubsequentTo(strategy)) { + return; + } + deletion_list.push_back(strategy); + } + } + + if (!deletion_list.empty()) { + // There are strategies strictly dominated within the p_current support profile. + // Drop them, and try again on the support restricted to undominated strategies. + StrategySupportProfile restricted(p_current); + for (auto strategy : deletion_list) { + restricted.RemoveStrategy(strategy); + } + PossibleNashSupports(p_support, restricted, cursor, p_result); + } + else { + // There are no strictly dominated strategies among the p_current support profile. + // This therefore could be a potential support profile. + if (AcceptCandidate(p_support, p_current)) { + p_result.push_back(p_current); + } + StrategySupportProfile::const_iterator c_copy(cursor); + do { + GameStrategy strategy = c_copy.GetStrategy(); + if (p_current.Contains(strategy) && + p_current.NumStrategies(strategy->GetPlayer()->GetNumber()) > 1) { + StrategySupportProfile restricted(p_current); + restricted.RemoveStrategy(strategy); + PossibleNashSupports(p_support, restricted, c_copy, p_result); + } + } while (c_copy.GoToNext()); + } +} + +} // end anonymous namespace + +std::shared_ptr +PossibleNashStrategySupports(const Game &p_game) +{ + auto result = std::make_shared(); + StrategySupportProfile support(p_game); + StrategySupportProfile sact(p_game); + StrategySupportProfile::const_iterator cursor(support); + PossibleNashSupports(support, sact, cursor, result->m_supports); + return result; +} From 6784a8e31f814d3ddd6b7fa2c46da19b74f9e46a Mon Sep 17 00:00:00 2001 From: Ted Turocy Date: Wed, 26 Jun 2024 13:18:39 +0100 Subject: [PATCH 11/99] This exposes enumpoly in pygambit for the first time. * Refactors and modernises the internal implementaton of enumpoly for strategic games * The heuristic-search version of enumpoly now uses the same per-support solver as the full enumeration --- ChangeLog | 2 + Makefile.am | 3 +- doc/pygambit.api.rst | 1 + src/pygambit/gambit.pxd | 8 + src/pygambit/nash.pxi | 12 + src/pygambit/nash.py | 32 ++ src/solvers/enumpoly/efgpoly.cc | 107 ++----- src/solvers/enumpoly/enumpoly.h | 76 +++++ src/solvers/enumpoly/nfgcpoly.cc | 359 --------------------- src/solvers/enumpoly/nfgcpoly.h | 85 ----- src/solvers/enumpoly/nfghs.cc | 332 +++----------------- src/solvers/enumpoly/nfghs.h | 49 ++- src/solvers/enumpoly/nfgpoly.cc | 518 ++++++------------------------- src/solvers/enumpoly/poly.imp | 5 +- src/tools/enumpoly/enumpoly.cc | 75 ++++- 15 files changed, 384 insertions(+), 1280 deletions(-) create mode 100644 src/solvers/enumpoly/enumpoly.h delete mode 100644 src/solvers/enumpoly/nfgcpoly.cc delete mode 100644 src/solvers/enumpoly/nfgcpoly.h diff --git a/ChangeLog b/ChangeLog index a9a52565d..7d05f3f67 100644 --- a/ChangeLog +++ b/ChangeLog @@ -12,6 +12,8 @@ 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`. ## [16.1.2] - unreleased diff --git a/Makefile.am b/Makefile.am index 0745f55c5..20d4539d0 100644 --- a/Makefile.am +++ b/Makefile.am @@ -471,12 +471,11 @@ gambit_enumpoly_SOURCES = \ src/solvers/enumpoly/sfstrat.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/enumpoly/enumpoly.cc gambit_enumpure_SOURCES = \ diff --git a/doc/pygambit.api.rst b/doc/pygambit.api.rst index 20e17f87e..6e5fb565f 100644 --- a/doc/pygambit.api.rst +++ b/doc/pygambit.api.rst @@ -279,6 +279,7 @@ Computation of Nash equilibria NashComputationResult enumpure_solve enummixed_solve + enumpoly_solve lp_solve lcp_solve liap_solve diff --git a/src/pygambit/gambit.pxd b/src/pygambit/gambit.pxd index aa8f0b463..87a5d3238 100644 --- a/src/pygambit/gambit.pxd +++ b/src/pygambit/gambit.pxd @@ -472,6 +472,14 @@ cdef extern from "solvers/nashsupport/nashsupport.h": c_Game ) except +RuntimeError +cdef extern from "solvers/enumpoly/enumpoly.h": + c_List[c_MixedStrategyProfileDouble] EnumPolyStrategySolve( + c_Game + ) except +RuntimeError + c_List[c_MixedBehaviorProfileDouble] EnumPolyBehaviorSolve( + c_Game + ) except +RuntimeError + cdef extern from "solvers/logit/logit.h": cdef cppclass c_LogitQREMixedBehaviorProfile "LogitQREMixedBehaviorProfile": c_LogitQREMixedBehaviorProfile(c_Game) except + diff --git a/src/pygambit/nash.pxi b/src/pygambit/nash.pxi index 6c1edd43f..c644b4d7f 100644 --- a/src/pygambit/nash.pxi +++ b/src/pygambit/nash.pxi @@ -192,6 +192,18 @@ def _nashsupport_strategy_solve(game: Game) -> typing.List[StrategySupportProfil return ret +def _enumpoly_strategy_solve( + game: Game +) -> typing.List[MixedStrategyProfileDouble]: + return _convert_mspd(EnumPolyStrategySolve(game.game)) + + +def _enumpoly_behavior_solve( + game: Game +) -> typing.List[MixedBehaviorProfileDouble]: + return _convert_mbpd(EnumPolyBehaviorSolve(game.game)) + + def _logit_strategy_solve( game: Game, maxregret: float, first_step: float, max_accel: float, ) -> typing.List[MixedStrategyProfileDouble]: diff --git a/src/pygambit/nash.py b/src/pygambit/nash.py index 530dffba6..2b6bda001 100644 --- a/src/pygambit/nash.py +++ b/src/pygambit/nash.py @@ -548,6 +548,38 @@ def possible_nash_supports(game: libgbt.Game) -> typing.List[libgbt.StrategySupp return libgbt._nashsupport_strategy_solve(game) +def enumpoly_solve( + game: libgbt.Game, + use_strategic: bool = False, +) -> NashComputationResult: + """Compute Nash equilibria by solving systems of polynomial equations. + + 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. + + Returns + ------- + res : NashComputationResult + The result represented as a ``NashComputationResult`` object. + """ + if not game.is_tree or use_strategic: + equilibria = libgbt._enumpoly_strategy_solve(game) + else: + equilibria = libgbt._enumpoly_behavior_solve(game) + return NashComputationResult( + game=game, + method="enumpoly", + rational=False, + use_strategic=not game.is_tree or use_strategic, + equilibria=equilibria, + ) + + def logit_solve( game: libgbt.Game, use_strategic: bool = False, diff --git a/src/solvers/enumpoly/efgpoly.cc b/src/solvers/enumpoly/efgpoly.cc index 610a2f221..92f87ceed 100644 --- a/src/solvers/enumpoly/efgpoly.cc +++ b/src/solvers/enumpoly/efgpoly.cc @@ -20,12 +20,9 @@ // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // -#include -#include -#include "gambit.h" - -using namespace Gambit; +#include +#include "enumpoly.h" #include "solvers/nashsupport/nashsupport.h" #include "sfg.h" #include "gpoly.h" @@ -34,8 +31,7 @@ using namespace Gambit; #include "quiksolv.h" #include "behavextend.h" -extern int g_numDecimals; -extern bool g_verbose; +using namespace Gambit; // // A class to organize the data needed to build the polynomials @@ -254,35 +250,19 @@ List> SolveSupport(const BehaviorSupportProfile &p_ // set up the rectangle of search Vector bottoms(data.nVars), tops(data.nVars); - bottoms = (double)0; - tops = (double)1; + bottoms = 0; + tops = 1; gRectangle Cube(bottoms, tops); QuikSolv quickie(equations); -#ifdef UNUSED - if (params.trace > 0) { - (*params.tracefile) << "\nThe equilibrium equations are \n" << quickie.UnderlyingEquations(); - } -#endif // UNUSED - - // 2147483647 = 2^31-1 = MaxInt - try { - if (quickie.FindCertainNumberOfRoots(Cube, 2147483647, 0)) { -#ifdef UNUSED - if (params.trace > 0) { - (*params.tracefile) << "\nThe system has the following roots in [0,1]^" << num_vars - << " :\n" - << quickie.RootList(); - } -#endif // UNUSED - } + quickie.FindCertainNumberOfRoots(Cube, std::numeric_limits::max(), 0); } catch (const Gambit::SingularMatrixException &) { p_isSingular = true; } catch (const Gambit::AssertionException &e) { - std::cerr << "Assertion warning: " << e.what() << std::endl; + // std::cerr << "Assertion warning: " << e.what() << std::endl; p_isSingular = true; } @@ -300,31 +280,6 @@ List> SolveSupport(const BehaviorSupportProfile &p_ return solutions; } -PVector SeqFormProbsFromSolVars(const ProblemData &p_data, const Vector &v) -{ - PVector x(p_data.SF.NumSequences()); - - for (int pl = 1; pl <= p_data.support.GetGame()->NumPlayers(); pl++) { - for (int seq = 1; seq <= p_data.SF.NumSequences()[pl]; seq++) { - x(pl, seq) = NumProbOfSequence(p_data, pl, seq, v); - } - } - - return x; -} - -void PrintProfile(std::ostream &p_stream, const std::string &p_label, - const MixedBehaviorProfile &p_profile) -{ - p_stream << p_label; - for (int i = 1; i <= p_profile.BehaviorProfileLength(); i++) { - p_stream.setf(std::ios::fixed); - p_stream << "," << std::setprecision(g_numDecimals) << p_profile[i]; - } - - p_stream << std::endl; -} - MixedBehaviorProfile ToFullSupport(const MixedBehaviorProfile &p_profile) { Game efg = p_profile.GetGame(); @@ -349,40 +304,19 @@ MixedBehaviorProfile ToFullSupport(const MixedBehaviorProfile &p return fullProfile; } -void PrintSupport(std::ostream &p_stream, const std::string &p_label, - const BehaviorSupportProfile &p_support) -{ - p_stream << p_label; - - for (int pl = 1; pl <= p_support.GetGame()->NumPlayers(); pl++) { - GamePlayer player = p_support.GetGame()->GetPlayer(pl); - - for (int iset = 1; iset <= player->NumInfosets(); iset++) { - GameInfoset infoset = player->GetInfoset(iset); +namespace Gambit { +namespace Nash { - p_stream << ","; - - for (int act = 1; act <= infoset->NumActions(); act++) { - if (p_support.Contains(infoset->GetAction(act))) { - p_stream << "1"; - } - else { - p_stream << "0"; - } - } - } - } - p_stream << std::endl; -} - -void EnumPolySolveExtensive(const Game &p_game) +List> +EnumPolyBehaviorSolve(const Game &p_game, + EnumPolyMixedBehaviorObserverFunctionType p_onEquilibrium, + EnumPolyBehaviorSupportObserverFunctionType p_onSupport) { + List> ret; auto possible_supports = PossibleNashBehaviorSupports(p_game); for (auto support : possible_supports->m_supports) { - if (g_verbose) { - PrintSupport(std::cout, "candidate", support); - } + p_onSupport("candidate", support); bool isSingular = false; List> newsolns = SolveSupport(support, isSingular); @@ -390,12 +324,17 @@ void EnumPolySolveExtensive(const Game &p_game) for (int j = 1; j <= newsolns.Length(); j++) { MixedBehaviorProfile fullProfile = ToFullSupport(newsolns[j]); if (fullProfile.GetLiapValue() < 1.0e-6) { - PrintProfile(std::cout, "NE", fullProfile); + p_onEquilibrium(fullProfile); + ret.push_back(fullProfile); } } - if (isSingular && g_verbose) { - PrintSupport(std::cout, "singular", support); + if (isSingular) { + p_onSupport("singular", support); } } + return ret; } + +} // namespace Nash +} // namespace Gambit diff --git a/src/solvers/enumpoly/enumpoly.h b/src/solvers/enumpoly/enumpoly.h new file mode 100644 index 000000000..25d16d601 --- /dev/null +++ b/src/solvers/enumpoly/enumpoly.h @@ -0,0 +1,76 @@ +// +// This file is part of Gambit +// Copyright (c) 1994-2024, The Gambit Project (http://www.gambit-project.org) +// +// FILE: src/solvers/enumpoly/enumpoly.h +// +// Finds Nash equilibria of a game by solving systems of polynomial equations +// by enumerating supports. +// +// 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_SOLVERS_ENUMPOLY_ENUMPOLY_H +#define GAMBIT_SOLVERS_ENUMPOLY_ENUMPOLY_H + +#include "games/nash.h" + +namespace Gambit { +namespace Nash { + +using EnumPolyMixedStrategyObserverFunctionType = + std::function &)>; + +inline void EnumPolyNullMixedStrategyObserver(const MixedStrategyProfile &) {} + +using EnumPolyStrategySupportObserverFunctionType = + std::function; + +inline void EnumPolyNullStrategySupportObserver(const std::string &, + const StrategySupportProfile &) +{ +} + +std::list> +EnumPolyStrategySupportSolve(const StrategySupportProfile &support, bool &is_singular, + int p_stopAfter = 0); + +List> EnumPolyStrategySolve( + const Game &, + EnumPolyMixedStrategyObserverFunctionType p_onEquilibrium = EnumPolyNullMixedStrategyObserver, + EnumPolyStrategySupportObserverFunctionType p_onSupport = EnumPolyNullStrategySupportObserver); + +using EnumPolyMixedBehaviorObserverFunctionType = + std::function &)>; + +inline void EnumPolyNullMixedBehaviorObserver(const MixedBehaviorProfile &) {} + +using EnumPolyBehaviorSupportObserverFunctionType = + std::function; + +inline void EnumPolyNullBehaviorSupportObserver(const std::string &, + const BehaviorSupportProfile &) +{ +} + +List> EnumPolyBehaviorSolve( + const Game &, + EnumPolyMixedBehaviorObserverFunctionType p_onEquilibrium = EnumPolyNullMixedBehaviorObserver, + EnumPolyBehaviorSupportObserverFunctionType p_onSupport = EnumPolyNullBehaviorSupportObserver); + +} // namespace Nash +} // namespace Gambit + +#endif // GAMBIT_SOLVERS_ENUMPOLY_ENUMPOLY_H diff --git a/src/solvers/enumpoly/nfgcpoly.cc b/src/solvers/enumpoly/nfgcpoly.cc deleted file mode 100644 index 36a135d33..000000000 --- a/src/solvers/enumpoly/nfgcpoly.cc +++ /dev/null @@ -1,359 +0,0 @@ -// -// This file is part of Gambit -// Copyright (c) 1994-2024, Litao Wei and The Gambit Project (http://www.gambit-project.org) -// -// FILE: src/tools/enumpoly/nfgcpoly.cc -// Compute Nash equilibria via heuristic search on game supports -// (Porter, Nudelman & Shoham, 2004) -// Implemented by Litao Wei -// -// 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 "nfgcpoly.h" - -//------------------------------------------------------------------------- -// HeuristicPolEnumModule: Member functions -//------------------------------------------------------------------------- - -HeuristicPolEnumModule::HeuristicPolEnumModule(const StrategySupportProfile &S, int p_stopAfter) - : m_stopAfter(p_stopAfter), NF(S.GetGame()), support(S), - Space(support.MixedProfileLength() - NF->NumPlayers()), Lex(&Space, lex), - num_vars(support.MixedProfileLength() - NF->NumPlayers()), nevals(0), is_singular(false) -{ - // gEpsilon(eps,12); -} - -int HeuristicPolEnumModule::PolEnum() -{ - gPolyList equations = NashOnSupportEquationsAndInequalities(); - - /* - // equations for equality of strat j to strat j+1 - for( i=1;i<=NF.NumPlayers();i++) - for(j=1;j2) - equations+=Prob(i,support.NumStrats(i)); - */ - - // set up the rectangle of search - Vector bottoms(num_vars), tops(num_vars); - bottoms = (double)0; - tops = (double)1; - - gRectangle Cube(bottoms, tops); - - // start QuikSolv - Gambit::List> solutionlist = NashOnSupportSolnVectors(equations, Cube); - - int index = SaveSolutions(solutionlist); - return index; -} - -int HeuristicPolEnumModule::SaveSolutions(const Gambit::List> &list) -{ - MixedStrategyProfile profile(support.NewMixedStrategyProfile()); - int i, j, k, kk, index = 0; - double sum; - - for (k = 1; k <= list.Length(); k++) { - kk = 0; - for (i = 1; i <= NF->NumPlayers(); i++) { - sum = 0; - for (j = 1; j < support.NumStrategies(i); j++) { - profile[support.GetStrategy(i, j)] = list[k][j + kk]; - sum += profile[support.GetStrategy(i, j)]; - } - profile[support.GetStrategy(i, j)] = (double)1.0 - sum; - kk += (support.NumStrategies(i) - 1); - } - solutions.push_back(profile); - index = solutions.size(); - } - return index; -} - -bool HeuristicPolEnumModule::EqZero(double x) const -{ - if (x <= eps && x >= -eps) { - return true; - } - return false; -} - -long HeuristicPolEnumModule::NumEvals() const { return nevals; } - -double HeuristicPolEnumModule::Time() const { return time; } - -const Gambit::List> &HeuristicPolEnumModule::GetSolutions() const -{ - return solutions; -} - -gPoly HeuristicPolEnumModule::Prob(int p, int strat) const -{ - gPoly equation(&Space, &Lex); - Vector exps(num_vars); - int i, j, kk = 0; - - for (i = 1; i < p; i++) { - kk += (support.NumStrategies(i) - 1); - } - - if (strat < support.NumStrategies(p)) { - exps = 0; - exps[strat + kk] = 1; - exp_vect const_exp(&Space, exps); - gMono const_term((double)1, const_exp); - gPoly new_term(&Space, const_term, &Lex); - equation += new_term; - } - else { - for (j = 1; j < support.NumStrategies(p); j++) { - exps = 0; - exps[j + kk] = 1; - exp_vect exponent(&Space, exps); - gMono term((double)(-1), exponent); - gPoly new_term(&Space, term, &Lex); - equation += new_term; - } - exps = 0; - exp_vect const_exp(&Space, exps); - gMono const_term((double)1, const_exp); - gPoly new_term(&Space, const_term, &Lex); - equation += new_term; - } - return equation; -} - -gPoly HeuristicPolEnumModule::IndifferenceEquation(int i, int strat1, int strat2) const -{ - gPoly equation(&Space, &Lex); - - for (StrategyProfileIterator A(support, i, strat1), B(support, i, strat2); !A.AtEnd(); - A++, B++) { - gPoly term(&Space, (double)1, &Lex); - for (int k = 1; k <= NF->NumPlayers(); k++) { - if (i != k) { - term *= Prob(k, support.GetStrategies(NF->GetPlayer(k)).Find((*A)->GetStrategy(k))); - } - } - double coeff, ap, bp; - ap = (*A)->GetPayoff(i); - bp = (*B)->GetPayoff(i); - coeff = ap - bp; - term *= coeff; - equation += term; - } - return equation; -} - -gPolyList HeuristicPolEnumModule::IndifferenceEquations() const -{ - gPolyList equations(&Space, &Lex); - - for (int pl = 1; pl <= NF->NumPlayers(); pl++) { - for (int j = 1; j < support.NumStrategies(pl); j++) { - equations += IndifferenceEquation(pl, j, j + 1); - } - } - - return equations; -} - -gPolyList HeuristicPolEnumModule::LastActionProbPositiveInequalities() const -{ - gPolyList equations(&Space, &Lex); - - for (int pl = 1; pl <= NF->NumPlayers(); pl++) { - if (support.NumStrategies(pl) > 2) { - equations += Prob(pl, support.NumStrategies(pl)); - } - } - - return equations; -} - -gPolyList HeuristicPolEnumModule::NashOnSupportEquationsAndInequalities() const -{ - gPolyList equations(&Space, &Lex); - - equations += IndifferenceEquations(); - equations += LastActionProbPositiveInequalities(); - - return equations; -} - -Gambit::List> -HeuristicPolEnumModule::NashOnSupportSolnVectors(const gPolyList &equations, - const gRectangle &Cube) -{ - QuikSolv quickie(equations); - // p_status.SetProgress(0); - - try { - quickie.FindCertainNumberOfRoots(Cube, 2147483647, m_stopAfter); - } - catch (SingularMatrixException &) { - is_singular = true; - } - - return quickie.RootList(); -} - -bool HeuristicPolEnumModule::IsSingular() const { return is_singular; } - -//--------------------------------------------------------------------------- -// PolEnumParams: member functions -//--------------------------------------------------------------------------- - -int PolEnum(const StrategySupportProfile &support, int p_stopAfter, - Gambit::List> &solutions, long &nevals, double &time, - bool &is_singular) -{ - HeuristicPolEnumModule module(support, p_stopAfter); - module.PolEnum(); - nevals = module.NumEvals(); - time = module.Time(); - solutions = module.GetSolutions(); - if (module.IsSingular()) { - is_singular = true; - } - else { - is_singular = false; - } - return 1; -} - -//--------------------------------------------------------------------------- -// Polish Equilibrum for Nfg -//--------------------------------------------------------------------------- - -#ifdef UNUSED -static MixedStrategyProfile PolishEquilibrium(const StrategySupportProfile &support, - const MixedStrategyProfile &sol, - bool &is_singular) -{ - HeuristicPolEnumModule module(support, 0); - Vector vec = module.SolVarsFromMixedStrategyProfile(sol); - - /* //DEBUG - gbtPVector xx = module.SeqFormProbsFromSolVars(vec); - MixedStrategyProfile newsol = module.SequenceForm().ToMixed(xx); - - gout << "sol.Profile = " << *(sol.Profile()) << "\n"; - gout << "vec = " << vec << "\n"; - gout << "xx = " << xx << "\n"; - gout << "newsol = " << newsol << "\n"; - - exit(0); - if ( newsol != *(sol.Profile()) ) { - gout << "Failure of reversibility in PolishEquilibrium.\n"; - exit(0); - } - */ - - // DEBUG - // gout << "Prior to Polishing vec is " << vec << ".\n"; - - module.PolishKnownRoot(vec); - - // DEBUG - // gout << "After Polishing vec is " << vec << ".\n"; - - return module.ReturnPolishedSolution(vec); -} - -#endif // UNUSED - -Vector HeuristicPolEnumModule::SolVarsFromMixedStrategyProfile( - const MixedStrategyProfile &sol) const -{ - int numvars(0); - - for (int pl = 1; pl <= NF->NumPlayers(); pl++) { - numvars += support.NumStrategies(pl) - 1; - } - - Vector answer(numvars); - int count(0); - - for (int pl = 1; pl <= NF->NumPlayers(); pl++) { - for (int j = 1; j < support.NumStrategies(pl); j++) { - count++; - answer[count] = (double)sol[support.GetStrategy(pl, j)]; - } - } - - return answer; -} - -int HeuristicPolEnumModule::PolishKnownRoot(Vector &point) const -{ - // DEBUG - // gout << "Prior to Polishing point is " << point << ".\n"; - - if (point.Length() > 0) { - // equations for equality of strat j to strat j+1 - gPolyList equations(&Space, &Lex); - equations += IndifferenceEquations(); - - // DEBUG - // gout << "We are about to construct quickie with Dmnsn() = " - // << Space->Dmnsn() << " and equations = \n" - // << equations << "\n"; - - // start QuikSolv - QuikSolv quickie(equations); - - // DEBUG - // gout << "We constructed quickie.\n"; - - try { - point = quickie.NewtonPolishedRoot(point); - } - catch (SingularMatrixException &) { - return 0; - } - - // DEBUG - // gout << "After Polishing point = " << point << ".\n"; - } - - return 1; -} - -MixedStrategyProfile -HeuristicPolEnumModule::ReturnPolishedSolution(const Vector &root) const -{ - MixedStrategyProfile profile(support.NewMixedStrategyProfile()); - - int j; - int kk = 0; - for (int pl = 1; pl <= NF->NumPlayers(); pl++) { - double sum = 0; - for (j = 1; j < support.NumStrategies(pl); j++) { - profile[support.GetStrategy(pl, j)] = root[j + kk]; - sum += profile[support.GetStrategy(pl, j)]; - } - profile[support.GetStrategy(pl, j)] = (double)1.0 - sum; - kk += (support.NumStrategies(pl) - 1); - } - - return profile; -} diff --git a/src/solvers/enumpoly/nfgcpoly.h b/src/solvers/enumpoly/nfgcpoly.h deleted file mode 100644 index fa24ac224..000000000 --- a/src/solvers/enumpoly/nfgcpoly.h +++ /dev/null @@ -1,85 +0,0 @@ -// -// This file is part of Gambit -// Copyright (c) 1994-2024, Litao Wei and The Gambit Project (http://www.gambit-project.org) -// -// FILE: src/tools/enumpoly/nfgcpoly.h -// Compute Nash equilibria via heuristic search on game supports -// (Porter, Nudelman & Shoham, 2004) -// Implemented by Litao Wei -// -// 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 NFGCPOLY_H -#define NFGCPOLY_H - -#include "gambit.h" -#include "solvers/nashsupport/nashsupport.h" -#include "gpoly.h" -#include "gpolylst.h" -#include "rectangl.h" -#include "quiksolv.h" - -using namespace Gambit; - -class HeuristicPolEnumModule { -private: - int m_stopAfter; - double eps{0.0}; - Game NF; - const StrategySupportProfile &support; - gSpace Space; - term_order Lex; - int num_vars; - long nevals; - double time{0.0}; - Gambit::List> solutions; - bool is_singular; - - bool EqZero(double x) const; - - // p_i_j as a gPoly, with last prob in terms of previous probs - gPoly Prob(int i, int j) const; - - // equation for when player i sets strat1 = strat2 - // with last probs for each player substituted out. - gPoly IndifferenceEquation(int i, int strat1, int strat2) const; - gPolyList IndifferenceEquations() const; - gPolyList LastActionProbPositiveInequalities() const; - gPolyList NashOnSupportEquationsAndInequalities() const; - Gambit::List> NashOnSupportSolnVectors(const gPolyList &equations, - const gRectangle &Cube); - - int SaveSolutions(const Gambit::List> &list); - -public: - HeuristicPolEnumModule(const StrategySupportProfile &, int p_stopAfter); - - int PolEnum(); - - long NumEvals() const; - double Time() const; - - const Gambit::List> &GetSolutions() const; - Vector SolVarsFromMixedStrategyProfile(const MixedStrategyProfile &) const; - - int PolishKnownRoot(Vector &) const; - - MixedStrategyProfile ReturnPolishedSolution(const Vector &) const; - - bool IsSingular() const; -}; - -#endif // NFGCPOLY_H diff --git a/src/solvers/enumpoly/nfghs.cc b/src/solvers/enumpoly/nfghs.cc index 4b1efe690..9b0cfb198 100644 --- a/src/solvers/enumpoly/nfghs.cc +++ b/src/solvers/enumpoly/nfghs.cc @@ -24,10 +24,10 @@ #include #include "gambit.h" -#include "nfgcpoly.h" #include "nfghs.h" +#include "enumpoly.h" -extern MixedStrategyProfile ToFullSupport(const MixedStrategyProfile &p_profile); +using namespace Nash; extern void PrintProfile(std::ostream &p_stream, const std::string &p_label, const MixedStrategyProfile &p_profile); @@ -38,7 +38,6 @@ extern void PrintProfile(std::ostream &p_stream, const std::string &p_label, void gbtNfgHs::Solve(const Game &game) { - int i; int size, diff, maxsize, maxdiff; bool preferBalance; @@ -58,7 +57,7 @@ void gbtNfgHs::Solve(const Game &game) preferBalance = false; } - Gambit::List> solutions; + List> solutions; // Iterate over possible sizes and differences of the support size profile. // The order of iteration is determined by preferBalance. @@ -105,7 +104,7 @@ void gbtNfgHs::Initialize(const Game &p_game) // remove strict dominated strategies - Gambit::Array strategies_list; + Array strategies_list; numPlayers = p_game->NumPlayers(); numActions = p_game->NumStrategies(); @@ -129,27 +128,14 @@ void gbtNfgHs::Cleanup(const Game &game) {} gbtNfgHs::gbtNfgHs(int p_stopAfter) : m_iteratedRemoval(true), m_removalWhenUninstantiated(1), m_ordering("automatic"), m_stopAfter(p_stopAfter) -#ifdef DEBUG - , - m_logfile(std::cerr) -#endif // DEBUG { } -void gbtNfgHs::SolveSizeDiff(const Game &game, - Gambit::List> &solutions, int size, - int diff) +void gbtNfgHs::SolveSizeDiff(const Game &game, List> &solutions, + int size, int diff) { - - //------------------------------------ - // Logging -#ifdef DEBUG - m_logfile << "Entering SolveSizeDiff with size = " << size << ", diff = " << diff << "\n"; -#endif - //------------------------------------ - int i, j; - Gambit::Array supportSizeProfile(numPlayers); + Array supportSizeProfile(numPlayers); for (i = 1; i <= numPlayers; i++) { supportSizeProfile[i] = 1; } @@ -207,51 +193,24 @@ void gbtNfgHs::SolveSizeDiff(const Game &game, SolveSupportSizeProfile(game, solutions, supportSizeProfile); } if (m_stopAfter > 0 && solutions.Length() >= m_stopAfter) { - //------------------------------------ - // Logging -#ifdef DEBUG - m_logfile << "Exiting SolveSizeDiff1\n"; -#endif - //------------------------------------ return; } } - - //------------------------------------ - // Logging -#ifdef DEBUG - m_logfile << "Exiting SolveSizeDiff2\n"; -#endif - //------------------------------------ } bool gbtNfgHs::SolveSupportSizeProfile(const Game &game, - Gambit::List> &solutions, - const Gambit::Array &supportSizeProfile) + List> &solutions, + const Array &supportSizeProfile) { + Array> uninstantiatedSupports(numPlayers); + Array>> domains(numPlayers); + PVector playerSupport(supportSizeProfile); - //------------------------------------ - // Logging -#ifdef DEBUG - m_logfile << "\n************\n"; - m_logfile << "* Solving support size profile: "; for (int i = 1; i <= numPlayers; i++) { - m_logfile << supportSizeProfile[i] << ' '; - } - m_logfile << "\n************\n"; -#endif - //------------------------------------ - - Gambit::Array> uninstantiatedSupports(numPlayers); - Gambit::Array>> domains(numPlayers); - Gambit::PVector playerSupport(supportSizeProfile); - - for (int i = 1; i <= numPlayers; i++) { - uninstantiatedSupports[i] = Gambit::Array(); + uninstantiatedSupports[i] = Array(); int m = 1; for (int j = 1; j <= supportSizeProfile[i]; j++) { playerSupport(i, j) = m; - // m_logfile << "playerSupport: " << i << j << m << std::endl; m++; } } @@ -259,7 +218,7 @@ bool gbtNfgHs::SolveSupportSizeProfile(const Game &game, for (int i = 1; i <= numPlayers; i++) { bool success = false; do { - Gambit::Array supportBlock; + Array supportBlock; GetSupport(game, i, playerSupport.GetRow(i), supportBlock); domains[i].push_back(supportBlock); success = UpdatePlayerSupport(game, i, playerSupport); @@ -269,20 +228,8 @@ bool gbtNfgHs::SolveSupportSizeProfile(const Game &game, } void gbtNfgHs::GetSupport(const Game &game, int playerIdx, const Vector &support, - Gambit::Array &supportBlock) + Array &supportBlock) { - - //------------------------------------ - // Logging -#ifdef DEBUG - m_logfile << "* Enter GetSupport for player " << playerIdx << ", choose strategies: "; - for (int i = 1; i <= support.Length(); i++) { - m_logfile << support[i] << ' '; - } - m_logfile << "\n"; -#endif - //------------------------------------ - GamePlayer player = game->GetPlayer(playerIdx); for (int i = 1; i <= support.Length(); i++) { int strategyIdx = support[i]; @@ -290,22 +237,9 @@ void gbtNfgHs::GetSupport(const Game &game, int playerIdx, const Vector &su } } -bool gbtNfgHs::UpdatePlayerSupport(const Game &game, int playerIdx, - Gambit::PVector &playerSupport) +bool gbtNfgHs::UpdatePlayerSupport(const Game &game, int playerIdx, PVector &playerSupport) { - Vector support = playerSupport.GetRow(playerIdx); - - //------------------------------------ - // Logging - // m_logfile << "\n****\n"; - // m_logfile << "* Enter UpdatePlayerSupport for player " << playerIdx << ", current strategiess: - // "; for ( int i = 1; i <= support.Length(); i++ ) { - // m_logfile << support[i] << ' '; - //} - // m_logfile << "\n****\n"; - //------------------------------------ - int lastBit = support.Length(); while (true) { playerSupport(playerIdx, lastBit) += 1; @@ -323,9 +257,6 @@ bool gbtNfgHs::UpdatePlayerSupport(const Game &game, int playerIdx, idx--; } if (playerSupport(playerIdx, 1) == (numActions[playerIdx] + 1)) { -#ifdef DEBUG - m_logfile << "Update finished, return false.\n"; -#endif return false; } @@ -345,73 +276,22 @@ bool gbtNfgHs::UpdatePlayerSupport(const Game &game, int playerIdx, return true; } -bool gbtNfgHs::RecursiveBacktracking( - const Game &game, Gambit::List> &solutions, - Gambit::Array> &uninstantiatedSupports, - Gambit::Array>> &domains, - int idxNextSupport2Instantiate) +bool gbtNfgHs::RecursiveBacktracking(const Game &game, + List> &solutions, + Array> &uninstantiatedSupports, + Array>> &domains, + int idxNextSupport2Instantiate) { - - //------------------------------------ - // Logging -#ifdef DEBUG - m_logfile << "\n********\n"; - m_logfile << "* Begin RecursiveBacktracking to instantiate player " - << idxNextSupport2Instantiate; - m_logfile << "\n********\n"; -#endif - //------------------------------------ - int idx = idxNextSupport2Instantiate; if (idx == numPlayers + 1) { - //------------------------------------ - // Logging -#ifdef DEBUG - m_logfile << "\n****************\n"; - m_logfile << "* No more player. Instantiate finished. Now try to solve the restricted game:\n"; - for (int i = 1; i <= uninstantiatedSupports.Length(); i++) { - m_logfile << " Player " << i << ": "; - for (int j = 1; j <= uninstantiatedSupports[i].Length(); j++) { - m_logfile << uninstantiatedSupports[i][j]->GetNumber() << ' '; - } - m_logfile << "\n"; - } -#endif - //------------------------------------ - - bool success = FeasibilityProgram(game, solutions, uninstantiatedSupports); - //------------------------------------ - // Logging -#ifdef DEBUG - m_logfile << "* Whether solved : " << success << "\n"; - m_logfile << "****************\n"; -#endif - //------------------------------------ - - if (success) { - return true; - } - else { - return false; - } + return FeasibilityProgram(game, solutions, uninstantiatedSupports); } else { // m_logfile << "Player " << idx << " domains length: " << domains[idx].Length() << " @@\n"; int domainLength = domains[idx].Length(); for (int k = 1; k <= domainLength; k++) { uninstantiatedSupports[idx] = domains[idx][k]; - //------------------------------------ - // Logging -#ifdef DEBUG - m_logfile << "\nNow instantiate strategy for player " << idx << " :"; - for (int i = 1; i <= uninstantiatedSupports[idx].Length(); i++) { - m_logfile << uninstantiatedSupports[idx][i]->GetNumber() << ' '; - } - m_logfile << "\n"; -#endif - //------------------------------------ - - Gambit::Array>> newDomains(numPlayers); + Array>> newDomains(numPlayers); for (int ii = 1; ii <= idx; ii++) { newDomains[ii].push_back(uninstantiatedSupports[ii]); } @@ -423,11 +303,6 @@ bool gbtNfgHs::RecursiveBacktracking( success = IteratedRemovalStrictlyDominatedStrategies(game, newDomains); } if (success) { - // update domain - // for (int ii = idx + 1; ii <= numPlayers; ii++) { - // domains[ii] = newDomains[ii]; - // } - if (RecursiveBacktracking(game, solutions, uninstantiatedSupports, newDomains, idx + 1)) { if ((m_stopAfter > 0) && (solutions.Length() >= m_stopAfter)) { return true; @@ -441,37 +316,10 @@ bool gbtNfgHs::RecursiveBacktracking( } bool gbtNfgHs::IteratedRemovalStrictlyDominatedStrategies( - const Game &game, Gambit::Array>> &domains) + const Game &game, Array>> &domains) { - - //------------------------------------ - // Logging -#ifdef DEBUG - m_logfile << "\n********\n"; - m_logfile << "* Begin IteratedRemovalStrictlyDominatedStrategies:\n"; - m_logfile << " Current domains:\n"; - for (int i = 1; i <= numPlayers; i++) { - m_logfile << " Player " << i << " [" << domains[i].Length() << "]: "; - for (int j = 1; j <= domains[i].Length(); j++) { - for (int k = 1; k <= domains[i][j].Length(); k++) { - m_logfile << domains[i][j][k]->GetNumber(); - if (k != domains[i][j].Length()) { - m_logfile << ' '; - } - } - if (j != domains[i].Length()) { - m_logfile << " | "; - } - } - m_logfile << "\n"; - } - m_logfile << "********\n"; -#endif - //------------------------------------ - - // Gambit::Array< Gambit::Array< Gambit::Array< gbtNfgStrategy > > > domains(numPlayers); bool changed = false; - Gambit::Array> domainStrategies(numPlayers); + Array> domainStrategies(numPlayers); GetDomainStrategies(domains, domainStrategies); @@ -481,7 +329,6 @@ bool gbtNfgHs::IteratedRemovalStrictlyDominatedStrategies( GamePlayer player = game->GetPlayer(i); // construct restrict game - StrategySupportProfile dominatedGame(game); for (int pl = 1; pl <= numPlayers; pl++) { @@ -502,18 +349,12 @@ bool gbtNfgHs::IteratedRemovalStrictlyDominatedStrategies( if (straIdx == 0) { continue; } - // bool dominated = IsConditionalDominated(dominatedGame, domainStrategies, stra, true); - bool dominated = false; for (int aj = 1; aj <= numActions[i]; aj++) { if (aj != ai) { if (IsConditionalDominatedBy(dominatedGame, domainStrategies, stra, player->GetStrategy(aj), true)) { dominated = true; -#ifdef DEBUG - m_logfile << "Strategy " << ai << " is dominated by strategy " << aj << " in player " - << i << "\n"; -#endif break; } } @@ -521,63 +362,24 @@ bool gbtNfgHs::IteratedRemovalStrictlyDominatedStrategies( if (dominated) { bool success = RemoveFromDomain(domains, domainStrategies, i, straIdx); -#ifdef DEBUG - m_logfile << "* Now remove strategy " << ai << " in player " << i << " : "; - m_logfile << success << "\n"; -#endif changed = true; if (!success) { - //------------------------------------ - // Logging -#ifdef DEBUG - m_logfile << "\n********\n"; - m_logfile << "* End IteratedRemovalStrictlyDominatedStrategies:\n"; - m_logfile << " After IRSDS, domains exhausted!\n"; - m_logfile << "********\n"; -#endif - //------------------------------------ return false; } } } } - } while (changed == true); - - //------------------------------------ - // Logging -#ifdef DEBUG - m_logfile << "\n********\n"; - m_logfile << "* End IteratedRemovalStrictlyDominatedStrategies:\n"; - m_logfile << " After IRSDS, domains:\n"; - for (int i = 1; i <= numPlayers; i++) { - m_logfile << " Player " << i << " [" << domains[i].Length() << "]: "; - for (int j = 1; j <= domains[i].Length(); j++) { - for (int k = 1; k <= domains[i][j].Length(); k++) { - m_logfile << domains[i][j][k]->GetNumber(); - if (k != domains[i][j].Length()) { - m_logfile << ' '; - } - } - if (j != domains[i].Length()) { - m_logfile << " | "; - } - } - m_logfile << "\n"; - } - m_logfile << "********\n"; -#endif - //------------------------------------ + } while (changed); return true; } -void gbtNfgHs::GetDomainStrategies( - Gambit::Array>> &domains, - Gambit::Array> &domainStrategies) const +void gbtNfgHs::GetDomainStrategies(Array>> &domains, + Array> &domainStrategies) const { for (int i = 1; i <= numPlayers; i++) { - domainStrategies[i] = Gambit::Array(); + domainStrategies[i] = Array(); for (int j = 1; j <= domains[i].Length(); j++) { for (int k = 1; k <= domains[i][j].Length(); k++) { if (domainStrategies[i].Find(domains[i][j][k]) == 0) { // no found @@ -588,19 +390,11 @@ void gbtNfgHs::GetDomainStrategies( } } -bool gbtNfgHs::IsConditionalDominatedBy( - StrategySupportProfile &dominatedGame, - Gambit::Array> &domainStrategies, const GameStrategy &strategy, - const GameStrategy &checkStrategy, bool strict) +bool gbtNfgHs::IsConditionalDominatedBy(StrategySupportProfile &dominatedGame, + Array> &domainStrategies, + const GameStrategy &strategy, + const GameStrategy &checkStrategy, bool strict) { - - //------------------------------------ - // Logging - // m_logfile << "\n********\n"; - // m_logfile << "* Enter IsConditionalDominates:\n "; - // m_logfile << "\n********\n"; - //------------------------------------ - GamePlayer player = strategy->GetPlayer(); int playerIdx = player->GetNumber(); @@ -610,36 +404,12 @@ bool gbtNfgHs::IsConditionalDominatedBy( GameStrategy newStrategy = dominatedPlayer->GetStrategy(strategyIdx); GameStrategy newCheckStrategy = dominatedPlayer->GetStrategy(checkStrategyIdx); - // return newCheckStrategy->Dominates(newStrategy, strict); return dominatedGame.Dominates(newCheckStrategy, newStrategy, strict); } -bool gbtNfgHs::IsConditionalDominated(StrategySupportProfile &dominatedGame, - Gambit::Array> &domainStrategies, - const GameStrategy &strategy, bool strict) -{ - - //------------------------------------ - // Logging - // m_logfile << "\n********\n"; - // m_logfile << "* Enter IsConditionalDominated:\n "; - // m_logfile << "\n********\n"; - //------------------------------------ - - GamePlayer player = strategy->GetPlayer(); - int playerIdx = player->GetNumber(); - - int strategyIdx = strategy->GetNumber(); - GamePlayer dominatedPlayer = dominatedGame.GetGame()->GetPlayer(playerIdx); - GameStrategy newStrategy = dominatedPlayer->GetStrategy(strategyIdx); - - // return newStrategy->IsDominated(strict); - return dominatedGame.IsDominated(newStrategy, strict); -} - -bool gbtNfgHs::RemoveFromDomain(Gambit::Array>> &domains, - Gambit::Array> &domainStrategies, - int player, int removeStrategyIdx) +bool gbtNfgHs::RemoveFromDomain(Array>> &domains, + Array> &domainStrategies, int player, + int removeStrategyIdx) { GameStrategy removeStrategy = domainStrategies[player][removeStrategyIdx]; @@ -651,17 +421,11 @@ bool gbtNfgHs::RemoveFromDomain(Gambit::Array> &solutions, - Gambit::Array> &uninstantiatedSupports) const +bool gbtNfgHs::FeasibilityProgram(const Game &game, List> &solutions, + Array> &uninstantiatedSupports) const { StrategySupportProfile restrictedGame(game); for (int pl = 1; pl <= numPlayers; pl++) { @@ -672,14 +436,13 @@ bool gbtNfgHs::FeasibilityProgram( } } - Gambit::List> newSolutions; - HeuristicPolEnumModule module(restrictedGame, (m_stopAfter == 1) ? 1 : 0); - module.PolEnum(); - newSolutions = module.GetSolutions(); + bool is_singular; + auto newSolutions = + EnumPolyStrategySupportSolve(restrictedGame, is_singular, (m_stopAfter == 1) ? 1 : 0); bool gotSolutions = false; - for (int k = 1; k <= newSolutions.Length(); k++) { - MixedStrategyProfile solutionToTest = ToFullSupport(newSolutions[k]); + for (auto soln : newSolutions) { + MixedStrategyProfile solutionToTest = soln.ToFullSupport(); if (solutionToTest.GetLiapValue() < .01) { gotSolutions = true; PrintProfile(std::cout, "NE", solutionToTest); @@ -689,10 +452,5 @@ bool gbtNfgHs::FeasibilityProgram( } } } - if (gotSolutions) { - return true; - } - else { - return false; - } + return gotSolutions; } diff --git a/src/solvers/enumpoly/nfghs.h b/src/solvers/enumpoly/nfghs.h index 9450ef1cd..630002015 100644 --- a/src/solvers/enumpoly/nfghs.h +++ b/src/solvers/enumpoly/nfghs.h @@ -36,59 +36,48 @@ class gbtNfgHs { int m_removalWhenUninstantiated; std::string m_ordering; -#ifdef DEBUG - std::ostream m_logfile; -#endif // DEBUG - int minActions{0}; int maxActions{0}; int numPlayers{0}; - Gambit::Array numActions; + Array numActions; void Initialize(const Game &game); void Cleanup(const Game &game); - void SolveSizeDiff(const Game &game, Gambit::List> &solutions, - int size, int diff); + void SolveSizeDiff(const Game &game, List> &solutions, int size, + int diff); - bool SolveSupportSizeProfile(const Game &game, - Gambit::List> &solutions, - const Gambit::Array &supportSizeProfile); + bool SolveSupportSizeProfile(const Game &game, List> &solutions, + const Array &supportSizeProfile); void GetSupport(const Game &game, int playerIdx, const Vector &support, - Gambit::Array &supportBlock); + Array &supportBlock); - bool UpdatePlayerSupport(const Game &game, int playerIdx, Gambit::PVector &playerSupport); + bool UpdatePlayerSupport(const Game &game, int playerIdx, PVector &playerSupport); - bool RecursiveBacktracking(const Game &game, - Gambit::List> &solutions, - Gambit::Array> &uninstantiatedSupports, - Gambit::Array>> &domains, + bool RecursiveBacktracking(const Game &game, List> &solutions, + Array> &uninstantiatedSupports, + Array>> &domains, int idxNextSupport2Instantiate); - bool IteratedRemovalStrictlyDominatedStrategies( - const Game &game, Gambit::Array>> &domains); + bool IteratedRemovalStrictlyDominatedStrategies(const Game &game, + Array>> &domains); - void GetDomainStrategies(Gambit::Array>> &domains, - Gambit::Array> &domainStrategies) const; + void GetDomainStrategies(Array>> &domains, + Array> &domainStrategies) const; bool IsConditionalDominatedBy(StrategySupportProfile &dominatedGame, - Gambit::Array> &domainStrategies, + Array> &domainStrategies, const GameStrategy &strategy, const GameStrategy &checkStrategy, bool strict); - bool IsConditionalDominated(StrategySupportProfile &dominatedGame, - Gambit::Array> &domainStrategies, - const GameStrategy &strategy, bool strict); - - bool RemoveFromDomain(Gambit::Array>> &domains, - Gambit::Array> &domainStrategies, int player, + bool RemoveFromDomain(Array>> &domains, + Array> &domainStrategies, int player, int removeStrategyIdx); - bool - FeasibilityProgram(const Game &game, Gambit::List> &solutions, - Gambit::Array> &uninstantiatedSupports) const; + bool FeasibilityProgram(const Game &game, List> &solutions, + Array> &uninstantiatedSupports) const; public: gbtNfgHs(int = 1); diff --git a/src/solvers/enumpoly/nfgpoly.cc b/src/solvers/enumpoly/nfgpoly.cc index 796c8f478..1b89a1a5d 100644 --- a/src/solvers/enumpoly/nfgpoly.cc +++ b/src/solvers/enumpoly/nfgpoly.cc @@ -21,484 +21,152 @@ // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // -#include -#include +#include +#include +#include "enumpoly.h" #include "solvers/nashsupport/nashsupport.h" #include "gpoly.h" #include "gpolylst.h" #include "rectangl.h" #include "quiksolv.h" -extern int g_numDecimals; -extern bool g_verbose; +using namespace Gambit; -class PolEnumModule { -private: - double eps{0.0}; - Gambit::Game NF; - const Gambit::StrategySupportProfile &support; - gSpace Space; - term_order Lex; - int num_vars; - long nevals{0}; - double time{0.0}; - Gambit::List> solutions; - bool is_singular{false}; +namespace { - bool EqZero(double x) const; - - // p_i_j as a gPoly, with last prob in terms of previous probs - gPoly Prob(int i, int j) const; - - // equation for when player i sets strat1 = strat2 - // with last probs for each player substituted out. - gPoly IndifferenceEquation(int i, int strat1, int strat2) const; - gPolyList IndifferenceEquations() const; - gPolyList LastActionProbPositiveInequalities() const; - gPolyList NashOnSupportEquationsAndInequalities() const; - Gambit::List> NashOnSupportSolnVectors(const gPolyList &equations, - const gRectangle &Cube); - - int SaveSolutions(const Gambit::List> &list); - -public: - explicit PolEnumModule(const Gambit::StrategySupportProfile &); - - int PolEnum(); - - long NumEvals() const; - double Time() const; - - const Gambit::List> &GetSolutions() const; - Gambit::Vector - SolVarsFromMixedProfile(const Gambit::MixedStrategyProfile &) const; - - int PolishKnownRoot(Gambit::Vector &) const; - - Gambit::MixedStrategyProfile - ReturnPolishedSolution(const Gambit::Vector &) const; - - bool IsSingular() const; -}; - -//------------------------------------------------------------------------- -// PolEnumModule: Member functions -//------------------------------------------------------------------------- - -PolEnumModule::PolEnumModule(const Gambit::StrategySupportProfile &S) - : NF(S.GetGame()), support(S), Space(support.MixedProfileLength() - NF->NumPlayers()), - Lex(&Space, lex), num_vars(support.MixedProfileLength() - NF->NumPlayers()) +// The polynomial representation of each strategy probability, substituting in +// the sum-to-one equation for the probability of the last strategy for each player +std::map> BuildStrategyVariables(const gSpace &space, + const term_order &lex, + const StrategySupportProfile &support) { - // Gambit::Epsilon(eps,12); -} - -int PolEnumModule::PolEnum() -{ - gPolyList equations = NashOnSupportEquationsAndInequalities(); - - /* - // equations for equality of strat j to strat j+1 - for( i=1;i<=NF->NumPlayers();i++) - for(j=1;jNumPlayers();i++) - if(support.NumStrats(i)>2) - equations+=Prob(i,support.NumStrats(i)); - */ - - // set up the rectangle of search - Gambit::Vector bottoms(num_vars), tops(num_vars); - bottoms = (double)0; - tops = (double)1; - - gRectangle Cube(bottoms, tops); - - // start QuikSolv - Gambit::List> solutionlist = NashOnSupportSolnVectors(equations, Cube); - - int index = SaveSolutions(solutionlist); - return index; -} - -int PolEnumModule::SaveSolutions(const Gambit::List> &list) -{ - Gambit::MixedStrategyProfile profile(support.NewMixedStrategyProfile()); - int i, j, k, kk, index = 0; - double sum; - - for (k = 1; k <= list.Length(); k++) { - kk = 0; - for (i = 1; i <= NF->NumPlayers(); i++) { - sum = 0; - for (j = 1; j < support.NumStrategies(i); j++) { - profile[support.GetStrategy(i, j)] = list[k][j + kk]; - sum += profile[support.GetStrategy(i, j)]; + int index = 1; + std::map> strategy_poly; + for (auto player : support.GetGame()->GetPlayers()) { + auto strategies = support.GetStrategies(player); + gPoly residual(&space, 1, &lex); + for (auto strategy : strategies) { + if (strategy != strategies.back()) { + strategy_poly.try_emplace(strategy, &space, index, 1, &lex); + residual -= strategy_poly.at(strategy); + index++; + } + else { + strategy_poly.emplace(strategy, residual); } - profile[support.GetStrategy(i, j)] = (double)1.0 - sum; - kk += (support.NumStrategies(i) - 1); - } - solutions.push_back(profile); - index = solutions.size(); - } - return index; -} - -bool PolEnumModule::EqZero(double x) const -{ - if (x <= eps && x >= -eps) { - return true; - } - return false; -} - -long PolEnumModule::NumEvals() const { return nevals; } - -double PolEnumModule::Time() const { return time; } - -const Gambit::List> &PolEnumModule::GetSolutions() const -{ - return solutions; -} - -gPoly PolEnumModule::Prob(int p, int strat) const -{ - gPoly equation(&Space, &Lex); - Gambit::Vector exps(num_vars); - int i, j, kk = 0; - - for (i = 1; i < p; i++) { - kk += (support.NumStrategies(i) - 1); - } - - if (strat < support.NumStrategies(p)) { - exps = 0; - exps[strat + kk] = 1; - exp_vect const_exp(&Space, exps); - gMono const_term((double)1, const_exp); - gPoly new_term(&Space, const_term, &Lex); - equation += new_term; - } - else { - for (j = 1; j < support.NumStrategies(p); j++) { - exps = 0; - exps[j + kk] = 1; - exp_vect exponent(&Space, exps); - gMono term((double)(-1), exponent); - gPoly new_term(&Space, term, &Lex); - equation += new_term; } - exps = 0; - exp_vect const_exp(&Space, exps); - gMono const_term((double)1, const_exp); - gPoly new_term(&Space, const_term, &Lex); - equation += new_term; } - return equation; + return strategy_poly; } -gPoly PolEnumModule::IndifferenceEquation(int i, int strat1, int strat2) const +gPoly IndifferenceEquation(const gSpace &space, const term_order &lex, + const StrategySupportProfile &support, + const std::map> &strategy_poly, + const GameStrategy &s1, const GameStrategy &s2) { - gPoly equation(&Space, &Lex); + gPoly equation(&space, &lex); - for (Gambit::StrategyProfileIterator A(support, i, strat1), B(support, i, strat2); !A.AtEnd(); - A++, B++) { - gPoly term(&Space, (double)1, &Lex); - for (int k = 1; k <= NF->NumPlayers(); k++) { - if (i != k) { - term *= Prob(k, support.GetStrategies(NF->GetPlayer(k)).Find((*A)->GetStrategy(k))); + for (StrategyProfileIterator A(support, s1), B(support, s2); !A.AtEnd(); A++, B++) { + gPoly term(&space, 1, &lex); + for (auto player : support.GetGame()->GetPlayers()) { + if (player != s1->GetPlayer()) { + term *= strategy_poly.at((*A)->GetStrategy(player)); } } - double coeff, ap, bp; - ap = (*A)->GetPayoff(i); - bp = (*B)->GetPayoff(i); - coeff = ap - bp; - term *= coeff; + term *= (*A)->GetPayoff(s1->GetPlayer()) - (*B)->GetPayoff(s1->GetPlayer()); equation += term; } return equation; } -gPolyList PolEnumModule::IndifferenceEquations() const +gPolyList ConstructEquations(const gSpace &space, const term_order &lex, + const StrategySupportProfile &support, + const std::map> &strategy_poly) { - gPolyList equations(&Space, &Lex); - - for (int pl = 1; pl <= NF->NumPlayers(); pl++) { - for (int j = 1; j < support.NumStrategies(pl); j++) { - equations += IndifferenceEquation(pl, j, j + 1); + gPolyList equations(&space, &lex); + // Indifference equations between pairs of strategies for each player + for (auto player : support.GetPlayers()) { + auto strategies = support.GetStrategies(player); + for (auto s1 = strategies.begin(), s2 = std::next(strategies.begin()); s2 != strategies.end(); + ++s1, ++s2) { + equations += IndifferenceEquation(space, lex, support, strategy_poly, *s1, *s2); } } - - return equations; -} - -gPolyList PolEnumModule::LastActionProbPositiveInequalities() const -{ - gPolyList equations(&Space, &Lex); - - for (int pl = 1; pl <= NF->NumPlayers(); pl++) { - if (support.NumStrategies(pl) > 2) { - equations += Prob(pl, support.NumStrategies(pl)); - } + // Inequalities for last probability for each player + for (auto player : support.GetPlayers()) { + equations += strategy_poly.at(support.GetStrategies(player).back()); } - return equations; } -gPolyList PolEnumModule::NashOnSupportEquationsAndInequalities() const -{ - gPolyList equations(&Space, &Lex); +} // end anonymous namespace - equations += IndifferenceEquations(); - equations += LastActionProbPositiveInequalities(); +namespace Gambit { +namespace Nash { - return equations; -} - -Gambit::List> -PolEnumModule::NashOnSupportSolnVectors(const gPolyList &equations, - const gRectangle &Cube) +std::list> +EnumPolyStrategySupportSolve(const StrategySupportProfile &support, bool &is_singular, + int p_stopAfter /* = 0 */) { - QuikSolv quickie(equations); - // p_status.SetProgress(0); + gSpace Space(support.MixedProfileLength() - support.GetGame()->NumPlayers()); + term_order Lex(&Space, lex); + + auto strategy_poly = BuildStrategyVariables(Space, Lex, support); + gPolyList equations = ConstructEquations(Space, Lex, support, strategy_poly); + Vector bottoms(Space.Dmnsn()), tops(Space.Dmnsn()); + bottoms = 0; + tops = 1; + gRectangle cube(bottoms, tops); + QuikSolv solver(equations); + is_singular = false; try { - quickie.FindCertainNumberOfRoots(Cube, 2147483647, 0); + solver.FindCertainNumberOfRoots(cube, std::numeric_limits::max(), p_stopAfter); } - catch (const Gambit::SingularMatrixException &) { + catch (const SingularMatrixException &) { is_singular = true; } - catch (const Gambit::AssertionException &e) { - std::cerr << "Assertion warning: " << e.what() << std::endl; - is_singular = true; - } - - return quickie.RootList(); -} - -bool PolEnumModule::IsSingular() const { return is_singular; } - -//--------------------------------------------------------------------------- -// PolEnumParams: member functions -//--------------------------------------------------------------------------- - -int PolEnum(const Gambit::StrategySupportProfile &support, - Gambit::List> &solutions, long &nevals, - double &time, bool &is_singular) -{ - PolEnumModule module(support); - module.PolEnum(); - nevals = module.NumEvals(); - time = module.Time(); - solutions = module.GetSolutions(); - if (module.IsSingular()) { + catch (const AssertionException &e) { + // std::cerr << "Assertion warning: " << e.what() << std::endl; is_singular = true; } - else { - is_singular = false; - } - return 1; -} - -//--------------------------------------------------------------------------- -// Polish Equilibrum for Nfg -//--------------------------------------------------------------------------- - -#ifdef UNUSED -static Gambit::MixedStrategyProfile -PolishEquilibrium(const Gambit::StrategySupportProfile &support, - const Gambit::MixedStrategyProfile &sol, bool &is_singular) -{ - PolEnumModule module(support); - Gambit::Vector vec = module.SolVarsFromMixedProfile(sol); - - /* //DEBUG - Gambit::PVector xx = module.SeqFormProbsFromSolVars(vec); - Gambit::MixedStrategyProfile newsol = module.SequenceForm().ToMixed(xx); - - gout << "sol.Profile = " << *(sol.Profile()) << "\n"; - gout << "vec = " << vec << "\n"; - gout << "xx = " << xx << "\n"; - gout << "newsol = " << newsol << "\n"; - - exit(0); - if ( newsol != *(sol.Profile()) ) { - gout << "Failure of reversibility in PolishEquilibrium.\n"; - exit(0); - } - */ - - // DEBUG - // gout << "Prior to Polishing vec is " << vec << ".\n"; - - module.PolishKnownRoot(vec); - - // DEBUG - // gout << "After Polishing vec is " << vec << ".\n"; - - return module.ReturnPolishedSolution(vec); -} -#endif // UNUSED - -Gambit::Vector -PolEnumModule::SolVarsFromMixedProfile(const Gambit::MixedStrategyProfile &sol) const -{ - int numvars(0); - - for (int pl = 1; pl <= NF->NumPlayers(); pl++) { - numvars += support.NumStrategies(pl) - 1; - } - - Gambit::Vector answer(numvars); - int count(0); - - for (int pl = 1; pl <= NF->NumPlayers(); pl++) { - for (int j = 1; j < support.NumStrategies(pl); j++) { - count++; - answer[count] = (double)sol[support.GetStrategy(pl, j)]; - } - } - - return answer; -} - -int PolEnumModule::PolishKnownRoot(Gambit::Vector &point) const -{ - // DEBUG - // gout << "Prior to Polishing point is " << point << ".\n"; - - if (point.Length() > 0) { - // equations for equality of strat j to strat j+1 - gPolyList equations(&Space, &Lex); - equations += IndifferenceEquations(); - - // DEBUG - // gout << "We are about to construct quickie with Dmnsn() = " - // << Space->Dmnsn() << " and equations = \n" - // << equations << "\n"; - - // start QuikSolv - QuikSolv quickie(equations); - - // DEBUG - // gout << "We constructed quickie.\n"; - - try { - point = quickie.NewtonPolishedRoot(point); - } - catch (Gambit::SingularMatrixException &) { - return 0; - } - - // DEBUG - // gout << "After Polishing point = " << point << ".\n"; - } - return 1; -} - -Gambit::MixedStrategyProfile -PolEnumModule::ReturnPolishedSolution(const Gambit::Vector &root) const -{ - Gambit::MixedStrategyProfile profile(support.NewMixedStrategyProfile()); - - int j; - int kk = 0; - for (int pl = 1; pl <= NF->NumPlayers(); pl++) { - double sum = 0; - for (j = 1; j < support.NumStrategies(pl); j++) { - profile[support.GetStrategy(pl, j)] = root[j + kk]; - sum += profile[support.GetStrategy(pl, j)]; + std::list> solutions; + for (auto soln : solver.RootList()) { + solutions.emplace(solutions.end(), support.NewMixedStrategyProfile()); + for (auto mapping : strategy_poly) { + solutions.back()[mapping.first] = mapping.second.Evaluate(soln); } - profile[support.GetStrategy(pl, j)] = (double)1.0 - sum; - kk += (support.NumStrategies(pl) - 1); - } - - return profile; -} - -void PrintProfile(std::ostream &p_stream, const std::string &p_label, - const Gambit::MixedStrategyProfile &p_profile) -{ - p_stream << p_label; - for (int i = 1; i <= p_profile.MixedProfileLength(); i++) { - p_stream.setf(std::ios::fixed); - p_stream << ',' << std::setprecision(g_numDecimals) << p_profile[i]; } - - p_stream << std::endl; -} - -Gambit::MixedStrategyProfile -ToFullSupport(const Gambit::MixedStrategyProfile &p_profile) -{ - Gambit::Game nfg = p_profile.GetGame(); - const Gambit::StrategySupportProfile &support = p_profile.GetSupport(); - - Gambit::MixedStrategyProfile fullProfile(nfg->NewMixedStrategyProfile(0.0)); - for (int i = 1; i <= fullProfile.MixedProfileLength(); fullProfile[i++] = 0.0) - ; - - int index = 1; - for (int pl = 1; pl <= nfg->NumPlayers(); pl++) { - Gambit::GamePlayer player = nfg->GetPlayer(pl); - for (int st = 1; st <= player->NumStrategies(); st++) { - if (support.Contains(player->GetStrategy(st))) { - fullProfile[player->GetStrategy(st)] = p_profile[index++]; - } - } - } - - return fullProfile; -} - -void PrintSupport(std::ostream &p_stream, const std::string &p_label, - const Gambit::StrategySupportProfile &p_support) -{ - p_stream << p_label; - - for (int pl = 1; pl <= p_support.GetGame()->NumPlayers(); pl++) { - Gambit::GamePlayer player = p_support.GetGame()->GetPlayer(pl); - - p_stream << ","; - for (int st = 1; st <= player->NumStrategies(); st++) { - if (p_support.Contains(player->GetStrategy(st))) { - p_stream << "1"; - } - else { - p_stream << "0"; - } - } - } - p_stream << std::endl; + return solutions; } -void EnumPolySolveStrategic(const Gambit::Game &p_nfg) +List> +EnumPolyStrategySolve(const Game &p_game, + EnumPolyMixedStrategyObserverFunctionType p_onEquilibrium, + EnumPolyStrategySupportObserverFunctionType p_onSupport) { - auto possible_supports = PossibleNashStrategySupports(p_nfg); + List> ret; + auto possible_supports = PossibleNashStrategySupports(p_game); for (auto support : possible_supports->m_supports) { - long newevals = 0; - double newtime = 0.0; - Gambit::List> newsolns; - bool is_singular = false; - - if (g_verbose) { - PrintSupport(std::cout, "candidate", support); - } - - PolEnum(support, newsolns, newevals, newtime, is_singular); - - for (int j = 1; j <= newsolns.Length(); j++) { - Gambit::MixedStrategyProfile fullProfile = ToFullSupport(newsolns[j]); + p_onSupport("candidate", support); + bool is_singular; + for (auto solution : EnumPolyStrategySupportSolve(support, is_singular)) { + MixedStrategyProfile fullProfile = solution.ToFullSupport(); if (fullProfile.GetLiapValue() < 1.0e-6) { - PrintProfile(std::cout, "NE", fullProfile); + p_onEquilibrium(fullProfile); + ret.push_back(fullProfile); } } - if (is_singular && g_verbose) { - PrintSupport(std::cout, "singular", support); + if (is_singular) { + p_onSupport("singular", support); } } + return ret; } + +} // namespace Nash +} // namespace Gambit diff --git a/src/solvers/enumpoly/poly.imp b/src/solvers/enumpoly/poly.imp index 4205af4ad..068b52422 100644 --- a/src/solvers/enumpoly/poly.imp +++ b/src/solvers/enumpoly/poly.imp @@ -307,10 +307,7 @@ template T polynomial::LeadingCoefficient() const } } -template Gambit::List polynomial::CoefficientList() const -{ - return coeflist; -} +template Gambit::List polynomial::CoefficientList() const { return coeflist; } template polynomial polynomial::GcdWith(const polynomial &that) const { diff --git a/src/tools/enumpoly/enumpoly.cc b/src/tools/enumpoly/enumpoly.cc index 2d1d4f6d9..1395941b3 100644 --- a/src/tools/enumpoly/enumpoly.cc +++ b/src/tools/enumpoly/enumpoly.cc @@ -25,8 +25,12 @@ #include #include #include "gambit.h" +#include "solvers/enumpoly/enumpoly.h" #include "solvers/enumpoly/nfghs.h" +using namespace Gambit; +using namespace Gambit::Nash; + int g_numDecimals = 6; bool g_verbose = false; @@ -58,8 +62,61 @@ void PrintHelp(char *progname) exit(1); } -extern void EnumPolySolveStrategic(const Gambit::Game &); -extern void EnumPolySolveExtensive(const Gambit::Game &); +void PrintProfile(std::ostream &p_stream, const std::string &p_label, + const MixedStrategyProfile &p_profile) +{ + p_stream << p_label; + for (int i = 1; i <= p_profile.MixedProfileLength(); i++) { + p_stream.setf(std::ios::fixed); + p_stream << ',' << std::setprecision(g_numDecimals) << p_profile[i]; + } + p_stream << std::endl; +} + +void PrintSupport(std::ostream &p_stream, const std::string &p_label, + const StrategySupportProfile &p_support) +{ + if (!g_verbose) { + return; + } + p_stream << p_label; + for (auto player : p_support.GetGame()->GetPlayers()) { + p_stream << ","; + for (auto strategy : player->GetStrategies()) { + p_stream << ((p_support.Contains(strategy)) ? 1 : 0); + } + } + p_stream << std::endl; +} + +void PrintProfile(std::ostream &p_stream, const std::string &p_label, + const MixedBehaviorProfile &p_profile) +{ + p_stream << p_label; + for (int i = 1; i <= p_profile.BehaviorProfileLength(); i++) { + p_stream.setf(std::ios::fixed); + p_stream << "," << std::setprecision(g_numDecimals) << p_profile[i]; + } + p_stream << std::endl; +} + +void PrintSupport(std::ostream &p_stream, const std::string &p_label, + const BehaviorSupportProfile &p_support) +{ + if (!g_verbose) { + return; + } + p_stream << p_label; + for (auto player : p_support.GetGame()->GetPlayers()) { + for (auto infoset : player->GetInfosets()) { + p_stream << ","; + for (auto action : infoset->GetActions()) { + p_stream << ((p_support.Contains(action)) ? 1 : 0); + } + } + } + p_stream << std::endl; +} int main(int argc, char *argv[]) { @@ -140,11 +197,21 @@ int main(int argc, char *argv[]) algorithm.Solve(game); } else { - EnumPolySolveStrategic(game); + EnumPolyStrategySolve( + game, + [](const MixedStrategyProfile &eqm) { PrintProfile(std::cout, "NE", eqm); }, + [](const std::string &label, const StrategySupportProfile &support) { + PrintSupport(std::cout, label, support); + }); } } else { - EnumPolySolveExtensive(game); + EnumPolyBehaviorSolve( + game, + [](const MixedBehaviorProfile &eqm) { PrintProfile(std::cout, "NE", eqm); }, + [](const std::string &label, const BehaviorSupportProfile &support) { + PrintSupport(std::cout, label, support); + }); } return 0; } From b0eaf78880f7a9f4f79c1342ebb21c51f275f347 Mon Sep 17 00:00:00 2001 From: Ted Turocy Date: Fri, 19 Jul 2024 16:16:31 +0100 Subject: [PATCH 12/99] Dropped support for Python 3.8. This drops support for Python 3.8. In addition, * Updates pre-commit actions * Updates Python style for 3.9/3.10 now that 3.8 is not supported. --- .github/workflows/python.yml | 2 +- .pre-commit-config.yaml | 8 ++++---- ChangeLog | 3 +++ pyproject.toml | 2 +- setup.py | 5 ++--- src/pygambit/nash.py | 26 +++++++++++++------------- src/pygambit/qre.py | 10 +++++----- 7 files changed, 29 insertions(+), 27 deletions(-) diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index 3cb531daa..b4c0a8155 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.12'] steps: - uses: actions/checkout@v4 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6c78b4416..3642c94b1 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,21 +1,21 @@ 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 - id: check-yaml - id: check-added-large-files - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.1.13 + rev: v0.5.3 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 7d05f3f67..a9a72d845 100644 --- a/ChangeLog +++ b/ChangeLog @@ -2,6 +2,9 @@ ## [16.3.0] - unreleased +### 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 diff --git a/pyproject.toml b/pyproject.toml index 3c3ff6424..b71562d38 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ requires = ["setuptools", "wheel", "Cython"] [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"] diff --git a/setup.py b/setup.py index a3d66b661..94559c59d 100644 --- a/setup.py +++ b/setup.py @@ -111,14 +111,13 @@ def readme(): setuptools.setup( name="pygambit", - version="16.2.0", + version="16.3.0", description="The package for computation in game theory", long_description=readme(), classifiers=[ "Development Status :: 5 - Production/Stable", "Intended Audience :: Science/Research", "License :: OSI Approved :: GNU General Public License v2 or later (GPLv2+)", - "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", @@ -136,7 +135,7 @@ def readme(): "Source": "https://github.com/gambitproject/gambit", "Tracker": "https://github.com/gambitproject/gambit/issues", }, - python_requires=">=3.8", + python_requires=">=3.9", install_requires=[ "lxml", # used for reading/writing GTE files "numpy", diff --git a/src/pygambit/nash.py b/src/pygambit/nash.py index 2b6bda001..f6fe32d7b 100644 --- a/src/pygambit/nash.py +++ b/src/pygambit/nash.py @@ -23,13 +23,14 @@ A set of utilities for computing Nash equilibria """ +from __future__ import annotations + import dataclasses -import typing import pygambit.gambit as libgbt -MixedStrategyEquilibriumSet = typing.List[libgbt.MixedStrategyProfile] -MixedBehaviorEquilibriumSet = typing.List[libgbt.MixedBehaviorProfile] +MixedStrategyEquilibriumSet = list[libgbt.MixedStrategyProfile] +MixedBehaviorEquilibriumSet = list[libgbt.MixedBehaviorProfile] @dataclasses.dataclass(frozen=True) @@ -57,7 +58,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) @@ -143,8 +144,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 `. @@ -247,8 +248,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 +310,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 +371,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 +423,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,7 +527,7 @@ def gnm_solve( raise -def possible_nash_supports(game: libgbt.Game) -> typing.List[libgbt.StrategySupportProfile]: +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. diff --git a/src/pygambit/qre.py b/src/pygambit/qre.py index a11ee3c27..ea33e0189 100644 --- a/src/pygambit/qre.py +++ b/src/pygambit/qre.py @@ -22,8 +22,9 @@ """ A set of utilities for computing and analyzing quantal response equilbria """ +from __future__ import annotations + import math -import typing import scipy.optimize @@ -47,7 +48,7 @@ def logit_solve_branch( def logit_solve_lambda( game: libgbt.Game, - lam: typing.Union[float, typing.List[float]], + lam: float | list[float], use_strategic: bool = False, first_step: float = .03, max_accel: float = 1.1, @@ -238,13 +239,12 @@ def _estimate_behavior_empirical( def logit_estimate( - data: typing.Union[libgbt.MixedStrategyProfile, - libgbt.MixedBehaviorProfile], + data: libgbt.MixedStrategyProfile | libgbt.MixedBehaviorProfile, use_empirical: bool = False, local_max: bool = False, first_step: float = .03, max_accel: float = 1.1, -) -> typing.Union[LogitQREMixedStrategyFitResult, LogitQREMixedBehaviorFitResult]: +) -> LogitQREMixedStrategyFitResult | LogitQREMixedBehaviorFitResult: """Use maximum likelihood estimation to find the logit quantal response equilibrium which best fits empirical frequencies of play. From 737dfe5f35c5b7c4cc5621125d9537af6b5c1019 Mon Sep 17 00:00:00 2001 From: Ted Turocy Date: Fri, 19 Jul 2024 12:54:01 +0100 Subject: [PATCH 13/99] Rework sequence form implementation in enumpoly. This is a substantial reworking of the implementation of the sequence form concept currently inside enumpoly. This significantly simplifies the implementation through a combination of better organisation and the use of modern C++ features which did not exist when this implementation was first done. In due course a more polished version of this should become part of the game-representation library. This is progress towards that, but is not yet fully stabilised. --- Makefile.am | 5 +- src/solvers/enumpoly/efgpoly.cc | 75 ++++---- src/solvers/enumpoly/gnarray.h | 59 ------ src/solvers/enumpoly/gnarray.imp | 150 --------------- src/solvers/enumpoly/ndarray.h | 93 +++++++++ src/solvers/enumpoly/sfg.cc | 312 +++++++++---------------------- src/solvers/enumpoly/sfg.h | 129 +++++++++---- src/solvers/enumpoly/sfstrat.cc | 249 ------------------------ src/solvers/enumpoly/sfstrat.h | 150 --------------- 9 files changed, 312 insertions(+), 910 deletions(-) delete mode 100644 src/solvers/enumpoly/gnarray.h delete mode 100644 src/solvers/enumpoly/gnarray.imp create mode 100644 src/solvers/enumpoly/ndarray.h delete mode 100644 src/solvers/enumpoly/sfstrat.cc delete mode 100644 src/solvers/enumpoly/sfstrat.h diff --git a/Makefile.am b/Makefile.am index 20d4539d0..1b1cf5369 100644 --- a/Makefile.am +++ b/Makefile.am @@ -463,12 +463,9 @@ gambit_enumpoly_SOURCES = \ src/solvers/enumpoly/linrcomb.cc \ src/solvers/enumpoly/linrcomb.h \ src/solvers/enumpoly/linrcomb.imp \ - src/solvers/enumpoly/gnarray.h \ - src/solvers/enumpoly/gnarray.imp \ + src/solvers/enumpoly/ndarray.h \ src/solvers/enumpoly/sfg.cc \ src/solvers/enumpoly/sfg.h \ - src/solvers/enumpoly/sfstrat.cc \ - src/solvers/enumpoly/sfstrat.h \ src/solvers/enumpoly/odometer.cc \ src/solvers/enumpoly/odometer.h \ src/solvers/enumpoly/nfghs.cc \ diff --git a/src/solvers/enumpoly/efgpoly.cc b/src/solvers/enumpoly/efgpoly.cc index 92f87ceed..e7a09689f 100644 --- a/src/solvers/enumpoly/efgpoly.cc +++ b/src/solvers/enumpoly/efgpoly.cc @@ -57,11 +57,12 @@ ProblemData::ProblemData(const BehaviorSupportProfile &p_support) { int tnv = 0; for (int pl = 1; pl <= p_support.GetGame()->NumPlayers(); pl++) { - var[pl] = Array(SF.NumSequences(pl)); + GamePlayer player = p_support.GetGame()->GetPlayer(pl); + var[pl] = Array(SF.NumSequences(player)); var[pl][1] = 0; - for (int seq = 2; seq <= SF.NumSequences(pl); seq++) { - int act = SF.ActionNumber(pl, seq); - GameInfoset infoset = SF.GetInfoset(pl, seq); + for (int seq = 2; seq <= SF.NumSequences(player); seq++) { + int act = SF.ActionNumber(player, seq); + GameInfoset infoset = SF.GetInfoset(player, seq); if (act < p_support.NumActions(infoset)) { var[pl][seq] = ++tnv; } @@ -89,15 +90,12 @@ ProblemData::~ProblemData() // derivatives vanish, and that the sum of action probabilities at // each information set be less than one. -gPoly ProbOfSequence(const ProblemData &p_data, int p, int seq) +gPoly ProbOfSequence(const ProblemData &p_data, const GamePlayer &p_player, int seq) { gPoly equation(p_data.Space, p_data.Lex); Vector exps(p_data.nVars); - int isetrow = p_data.SF.InfosetRowNumber(p, seq); - int act = p_data.SF.ActionNumber(p, seq); - int varno = p_data.var[p][seq]; - GameInfoset infoset = p_data.SF.GetInfoset(p, seq); + GameInfoset infoset = p_data.SF.GetInfoset(p_player, seq); if (seq == 1) { exps = 0; @@ -106,21 +104,22 @@ gPoly ProbOfSequence(const ProblemData &p_data, int p, int seq) gPoly new_term(p_data.Space, const_term, p_data.Lex); equation += new_term; } - else if (act < p_data.support.NumActions(infoset)) { + else if (p_data.SF.ActionNumber(p_player, seq) < p_data.support.NumActions(infoset)) { exps = 0; - exps[varno] = 1; + exps[p_data.var[p_player->GetNumber()][seq]] = 1; exp_vect const_exp(p_data.Space, exps); gMono const_term(1.0, const_exp); gPoly new_term(p_data.Space, const_term, p_data.Lex); equation += new_term; } else { + int isetrow = p_data.SF.InfosetRowNumber(p_player, seq); for (int j = 1; j < seq; j++) { - if (p_data.SF.Constraints(p)(isetrow, j) == Rational(-1)) { - equation -= ProbOfSequence(p_data, p, j); + if (p_data.SF.GetConstraintEntry(p_player, isetrow, j) == Rational(-1)) { + equation -= ProbOfSequence(p_data, p_player, j); } - else if (p_data.SF.Constraints(p)(isetrow, j) == Rational(1)) { - equation += ProbOfSequence(p_data, p, j); + else if (p_data.SF.GetConstraintEntry(p_player, isetrow, j) == Rational(1)) { + equation += ProbOfSequence(p_data, p_player, j); } } } @@ -130,16 +129,15 @@ gPoly ProbOfSequence(const ProblemData &p_data, int p, int seq) gPoly GetPayoff(const ProblemData &p_data, int pl) { gIndexOdometer index(p_data.SF.NumSequences()); - Rational pay; gPoly equation(p_data.Space, p_data.Lex); while (index.Turn()) { - pay = p_data.SF.Payoff(index.CurrentIndices(), pl); + auto pay = p_data.SF.GetPayoff(index.CurrentIndices(), pl); if (pay != Rational(0)) { - gPoly term(p_data.Space, (double)pay, p_data.Lex); - int k; - for (k = 1; k <= p_data.support.GetGame()->NumPlayers(); k++) { - term *= ProbOfSequence(p_data, k, (index.CurrentIndices())[k]); + gPoly term(p_data.Space, double(pay), p_data.Lex); + for (int k = 1; k <= p_data.support.GetGame()->NumPlayers(); k++) { + term *= ProbOfSequence(p_data, p_data.support.GetGame()->GetPlayer(k), + index.CurrentIndices()[k]); } equation += term; } @@ -153,8 +151,9 @@ gPolyList IndifferenceEquations(const ProblemData &p_data) int kk = 0; for (int pl = 1; pl <= p_data.SF.NumPlayers(); pl++) { + GamePlayer player = p_data.support.GetGame()->GetPlayer(pl); gPoly payoff = GetPayoff(p_data, pl); - int n_vars = p_data.SF.NumSequences(pl) - p_data.SF.NumInfosets(pl) - 1; + int n_vars = p_data.SF.NumSequences(player) - p_data.SF.NumInfosets(player) - 1; for (int j = 1; j <= n_vars; j++) { equations += payoff.PartialDerivative(kk + j); } @@ -169,11 +168,12 @@ gPolyList LastActionProbPositiveInequalities(const ProblemData &p_data) gPolyList equations(p_data.Space, p_data.Lex); for (int i = 1; i <= p_data.SF.NumPlayers(); i++) { - for (int j = 2; j <= p_data.SF.NumSequences(i); j++) { - int act_num = p_data.SF.ActionNumber(i, j); - GameInfoset infoset = p_data.SF.GetInfoset(i, j); + GamePlayer player = p_data.support.GetGame()->GetPlayer(i); + for (int j = 2; j <= p_data.SF.NumSequences(player); j++) { + int act_num = p_data.SF.ActionNumber(player, j); + GameInfoset infoset = p_data.SF.GetInfoset(player, j); if (act_num == p_data.support.NumActions(infoset) && act_num > 1) { - equations += ProbOfSequence(p_data, i, j); + equations += ProbOfSequence(p_data, player, j); } } } @@ -195,27 +195,26 @@ gPolyList NashOnSupportEquationsAndInequalities(const ProblemData &p_dat // Mapping solution vectors to sequences //======================================================================= -double NumProbOfSequence(const ProblemData &p_data, int p, int seq, const Vector &x) +double NumProbOfSequence(const ProblemData &p_data, const GamePlayer &p_player, int seq, + const Vector &x) { - int isetrow = p_data.SF.InfosetRowNumber(p, seq); - int act = p_data.SF.ActionNumber(p, seq); - int varno = p_data.var[p][seq]; - GameInfoset infoset = p_data.SF.GetInfoset(p, seq); + GameInfoset infoset = p_data.SF.GetInfoset(p_player, seq); if (seq == 1) { return 1.0; } - else if (act < p_data.support.NumActions(infoset)) { - return x[varno]; + else if (p_data.SF.ActionNumber(p_player, seq) < p_data.support.NumActions(infoset)) { + return x[p_data.var[p_player->GetNumber()][seq]]; } else { double value = 0.0; + int isetrow = p_data.SF.InfosetRowNumber(p_player, seq); for (int j = 1; j < seq; j++) { - if (p_data.SF.Constraints(p)(isetrow, j) == Rational(-1)) { - value -= NumProbOfSequence(p_data, p, j, x); + if (p_data.SF.GetConstraintEntry(p_player, isetrow, j) == Rational(-1)) { + value -= NumProbOfSequence(p_data, p_player, j, x); } - else if (p_data.SF.Constraints(p)(isetrow, j) == Rational(1)) { - value += NumProbOfSequence(p_data, p, j, x); + else if (p_data.SF.GetConstraintEntry(p_player, isetrow, j) == Rational(1)) { + value += NumProbOfSequence(p_data, p_player, j, x); } } return value; @@ -228,7 +227,7 @@ PVector SeqFormVectorFromSolFormVector(const ProblemData &p_data, const for (int i = 1; i <= p_data.support.GetGame()->NumPlayers(); i++) { for (int j = 1; j <= p_data.SF.NumSequences()[i]; j++) { - x(i, j) = NumProbOfSequence(p_data, i, j, v); + x(i, j) = NumProbOfSequence(p_data, p_data.support.GetGame()->GetPlayer(i), j, v); } } diff --git a/src/solvers/enumpoly/gnarray.h b/src/solvers/enumpoly/gnarray.h deleted file mode 100644 index e8f6f61c4..000000000 --- a/src/solvers/enumpoly/gnarray.h +++ /dev/null @@ -1,59 +0,0 @@ -// -// This file is part of Gambit -// Copyright (c) 1994-2024, The Gambit Project (http://www.gambit-project.org) -// -// FILE: src/tools/enumpoly/gnarray.h -// Interface declaration for N-dimensional arrays -// -// 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 GNARRAY_H -#define GNARRAY_H - -#include "gambit.h" - -// -// Basic n-dimensional array -// -template class gNArray { -protected: - long storage_size; - T *storage; - Gambit::Array dim; - -public: - gNArray(); - explicit gNArray(const Gambit::Array &d); - gNArray(const gNArray &a); - ~gNArray(); - - gNArray &operator=(const gNArray &); - - /* not used for now - T operator[](const Gambit::Vector &) const; - T &operator[](const Gambit::Vector &); - */ - - T operator[](const Gambit::Array &) const; - T &operator[](const Gambit::Array &); - - const T &operator[](long l) const; - T &operator[](long l); - - const Gambit::Array &Dimensionality() const; -}; - -#endif // GNARRAY_H diff --git a/src/solvers/enumpoly/gnarray.imp b/src/solvers/enumpoly/gnarray.imp deleted file mode 100644 index 43d3a8e28..000000000 --- a/src/solvers/enumpoly/gnarray.imp +++ /dev/null @@ -1,150 +0,0 @@ -// -// This file is part of Gambit -// Copyright (c) 1994-2024, The Gambit Project (http://www.gambit-project.org) -// -// FILE: src/tools/enumpoly/gnarray.imp -// Implementation for N-dimensional arrays -// -// 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 "gnarray.h" - -template gNArray::gNArray() : storage_size(0), storage(nullptr) {} - -template gNArray::gNArray(const Gambit::Array &d) : dim(d) -{ - if (dim.Length() <= 0) { - storage = nullptr; - storage_size = 0; - } - else { - // assert(dim.First() == 1); - storage_size = 1; - int i; - for (i = 1; i <= dim.Length(); i++) { - // assert(dim[i] >= 1); - storage_size *= dim[i]; - } - - storage = new T[storage_size]; - for (i = 0; i < storage_size; storage[i++] = 0) - ; - } -} - -template -gNArray::gNArray(const gNArray &a) : storage_size(a.storage_size), dim(a.dim) -{ - storage = (storage_size > 0) ? new T[storage_size] : nullptr; - for (int i = 0; i < storage_size; i++) { - storage[i] = a.storage[i]; - } -} - -template gNArray::~gNArray() -{ - if (storage) { - delete[] storage; - } -} - -template gNArray &gNArray::operator=(const gNArray &a) -{ - if (this != &a) { - if (storage) { - delete[] storage; - } - dim = a.dim; - storage_size = a.storage_size; - storage = (storage_size > 0) ? new T[storage_size] : nullptr; - for (int i = 0; i < storage_size; i++) { - storage[i] = a.storage[i]; - } - } - return *this; -} - -/* -template T gNArray::operator[](const Gambit::Vector &v) const -{ - //assert(dim.Length() > 0 && dim.Length() == v.Length()); - int i,location,offset; - - for (i = 1, location = 0, offset = 1; i <= dim.Length(); i++) { - //assert(v[i] > 0 && v[i] <= dim[i]); - location += (v[i] - 1) * offset; - offset *= dim[i]; - } - - return storage[location]; -} - -template T &gNArray::operator[](const Gambit::Vector &v) -{ - //assert(dim.Length() > 0 && dim.Length() == v.Length()); - int i, location, offset; - - for (i = 1, location = 0, offset = 1; i <= dim.Length(); i++) { - //assert(v[i] > 0 && v[i] <= dim[i]); - location += (v[i] - 1) * offset; - offset *= dim[i]; - } - - return storage[location]; -} -*/ - -template T gNArray::operator[](const Gambit::Array &v) const -{ - // assert(dim.Length() > 0 && dim.Length() == v.Length()); - int i, location, offset; - - for (i = 1, location = 0, offset = 1; i <= dim.Length(); i++) { - // assert(v[i] > 0 && v[i] <= dim[i]); - location += (v[i] - 1) * offset; - offset *= dim[i]; - } - - return storage[location]; -} - -template T &gNArray::operator[](const Gambit::Array &v) -{ - // assert(dim.Length() > 0 && dim.Length() == v.Length()); - int i, location, offset; - - for (i = 1, location = 0, offset = 1; i <= dim.Length(); i++) { - // assert(v[i] > 0 && v[i] <= dim[i]); - location += (v[i] - 1) * offset; - offset *= dim[i]; - } - - return storage[location]; -} - -template const T &gNArray::operator[](long l) const -{ - // assert(l >= 0 && l < storage_size); - return storage[l]; -} - -template T &gNArray::operator[](long l) -{ - // assert(l >= 0 && l < storage_size); - return storage[l]; -} - -template const Gambit::Array &gNArray::Dimensionality() const { return dim; } diff --git a/src/solvers/enumpoly/ndarray.h b/src/solvers/enumpoly/ndarray.h new file mode 100644 index 000000000..8f0b5c264 --- /dev/null +++ b/src/solvers/enumpoly/ndarray.h @@ -0,0 +1,93 @@ +// +// This file is part of Gambit +// Copyright (c) 1994-2024, The Gambit Project (http://www.gambit-project.org) +// +// FILE: src/tools/enumpoly/gnarray.h +// Interface declaration for N-dimensional arrays +// +// 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 NDARRAY_H +#define NDARRAY_H + +#include + +#include "gambit.h" + +namespace Gambit { + +/// A representation of an (N+1)-dimensional array +/// +/// This class provides an (N+1)-dimensional array, or, if you prefer, an N-dimensional +/// array of vectors (where all vectors are of the same size). +/// Elements of the array are indexed using a pair of an Array (the index into the N-dimensional +/// part) and an integer (the index into the vector part). +/// +/// Internally, this is implemented as a single contiguous std::vector. The N+1 dimensions are +/// mapped into this vector using offsets (in a quite standard way); these offsets are pre-computed +/// and stored to make indexing efficient. +/// +/// @tparam T The data type of elements of the array +template class NDArray { +protected: + Array m_index_dim; + int m_vector_dim; + Array m_offsets; + int m_offsets_sum; + std::vector m_storage; + + int ComputeOffset(const Array &p_array_index, int p_vector_index) const + { + // Gambit Array indices are 1-based but std::vector is 0-based. + // As a result to index into the storage array we would need to deduct 1 from each + // element of the array index. This is the same as subtracting the sum of the offsets. + return (std::inner_product(p_array_index.begin(), p_array_index.end(), m_offsets.begin(), 0) + + p_vector_index * m_offsets.back() - m_offsets_sum); + } + +public: + NDArray() : m_vector_dim(0) {} + explicit NDArray(const Array &p_index_dim, int p_vector_dim) + : m_index_dim(p_index_dim), m_vector_dim(p_vector_dim), + m_storage( + std::accumulate(m_index_dim.begin(), m_index_dim.end(), 1, std::multiplies()) * + m_vector_dim), + m_offsets(p_index_dim.size() + 1) + { + m_offsets.front() = 1; + std::partial_sum(m_index_dim.begin(), m_index_dim.end(), std::next(m_offsets.begin()), + std::multiplies()); + m_offsets_sum = std::accumulate(m_offsets.begin(), m_offsets.end(), 0); + } + + NDArray(const NDArray &) = default; + ~NDArray() = default; + + NDArray &operator=(const NDArray &) = default; + + const T &at(const Array &v, int index) const + { + return m_storage.at(ComputeOffset(v, index)); + } + T &at(const Array &v, int index) { return m_storage.at(ComputeOffset(v, index)); } + + const Array &GetIndexDimension() const { return m_index_dim; } + int GetVectorDimension() { return m_vector_dim; } +}; + +} // end namespace Gambit + +#endif // NDARRAY_H diff --git a/src/solvers/enumpoly/sfg.cc b/src/solvers/enumpoly/sfg.cc index 003ecafb6..b564c6139 100644 --- a/src/solvers/enumpoly/sfg.cc +++ b/src/solvers/enumpoly/sfg.cc @@ -21,271 +21,133 @@ // #include "sfg.h" -#include "sfstrat.h" -#include "gnarray.imp" #include "gambit.h" -//---------------------------------------------------- -// Sfg: Constructors, Destructors, Operators -//---------------------------------------------------- +using namespace Gambit; -Sfg::Sfg(const Gambit::BehaviorSupportProfile &S) - : EF(S.GetGame()), efsupp(S), seq(EF->NumPlayers()), isetFlag(S.GetGame()->NumInfosets()), - isetRow(S.GetGame()->NumInfosets()), infosets(EF->NumPlayers()) -{ - int i; - Gambit::Array zero(EF->NumPlayers()); - Gambit::Array one(EF->NumPlayers()); - - Gambit::BehaviorSupportProfile support(EF); - - for (i = 1; i <= EF->NumPlayers(); i++) { - seq[i] = 1; - zero[i] = nullptr; - one[i] = 1; - } - - isetFlag = 0; - isetRow = 0; +namespace Gambit { - GetSequenceDims(EF->GetRoot()); - - isetFlag = 0; - - gIndexOdometer index(seq); - - SF = new gNArray *>(seq); - while (index.Turn()) { - (*SF)[index.CurrentIndices()] = new Gambit::Array(EF->NumPlayers()); - for (i = 1; i <= EF->NumPlayers(); i++) { - (*(*SF)[index.CurrentIndices()])[i] = (Gambit::Rational)0; - } +Sfg::Sfg(const BehaviorSupportProfile &S) : support(S) +{ + for (auto player : support.GetGame()->GetPlayers()) { + m_actionParents[player] = {}; } - E = new Gambit::Array *>(EF->NumPlayers()); - for (i = 1; i <= EF->NumPlayers(); i++) { - (*E)[i] = new Gambit::RectArray(infosets[i].Length() + 1, seq[i]); - for (int j = (*(*E)[i]).MinRow(); j <= (*(*E)[i]).MaxRow(); j++) { - for (int k = (*(*E)[i]).MinCol(); k <= (*(*E)[i]).MaxCol(); k++) { - (*(*E)[i])(j, k) = (Gambit::Rational)0; - } - } - (*(*E)[i])(1, 1) = (Gambit::Rational)1; + PureSequenceProfile parent; + for (auto player : support.GetGame()->GetPlayers()) { + parent[player] = nullptr; } + BuildSequences(support.GetGame()->GetRoot(), parent); - sequences = new Gambit::Array(EF->NumPlayers()); - for (i = 1; i <= EF->NumPlayers(); i++) { - (*sequences)[i] = new SFSequenceSet(EF->GetPlayer(i)); + Array dim(support.GetGame()->NumPlayers()); + for (int pl = 1; pl <= support.GetGame()->NumPlayers(); pl++) { + dim[pl] = m_actionParents[support.GetGame()->GetPlayer(pl)].size() + 1; } + SF = NDArray(dim, support.GetGame()->NumPlayers()); - Gambit::Array parent(EF->NumPlayers()); - for (i = 1; i <= EF->NumPlayers(); i++) { - parent[i] = (((*sequences)[i])->GetSFSequenceSet())[1]; + for (auto player : support.GetGame()->GetPlayers()) { + m_sequenceColumns[player] = {{nullptr, 1}}; + E.try_emplace(player, infoset_row[player].size() + 1, m_actionParents[player].size() + 1); + E.at(player)(1, 1) = Rational(1); + parent[player] = nullptr; } - MakeSequenceForm(EF->GetRoot(), (Gambit::Rational)1, one, zero, parent); + FillTableau(support.GetGame()->GetRoot(), Rational(1), parent); } -Sfg::~Sfg() +Rational &Sfg::GetMatrixEntry(const PureSequenceProfile &profile, const GamePlayer &player) { - gIndexOdometer index(seq); - - while (index.Turn()) { - delete (*SF)[index.CurrentIndices()]; - } - delete SF; - - int i; - - for (i = 1; i <= EF->NumPlayers(); i++) { - delete (*E)[i]; - } - delete E; - - for (i = 1; i <= EF->NumPlayers(); i++) { - delete (*sequences)[i]; + Array index(profile.size()); + for (int pl = 1; pl <= support.GetGame()->NumPlayers(); pl++) { + index[pl] = m_sequenceColumns.at(support.GetGame()->GetPlayer(pl)) + .at(profile.at(support.GetGame()->GetPlayer(pl))); } - delete sequences; + return SF.at(index, player->GetNumber()); } -void Sfg::MakeSequenceForm(const Gambit::GameNode &n, const Gambit::Rational &prob, - Gambit::Array seq, Gambit::Array iset, - Gambit::Array parent) +Rational &Sfg::GetConstraintEntry(const GameInfoset &infoset, const GameAction &action) { - int i, pl; + return E.at(infoset->GetPlayer())(infoset_row[infoset->GetPlayer()][infoset], + m_sequenceColumns[infoset->GetPlayer()][action]); +} - if (n->GetOutcome()) { - for (pl = 1; pl <= seq.Length(); pl++) { - (*(*SF)[seq])[pl] += prob * static_cast(n->GetOutcome()->GetPayoff(pl)); - } +void Sfg::BuildSequences(const GameNode &n, PureSequenceProfile &p_previousActions) +{ + if (!n->GetInfoset()) { + return; } - if (n->GetInfoset()) { - if (n->GetPlayer()->IsChance()) { - for (i = 1; i <= n->NumChildren(); i++) { - MakeSequenceForm(n->GetChild(i), - prob * static_cast(n->GetInfoset()->GetActionProb(i)), - seq, iset, parent); - } - } - else { - int pl = n->GetPlayer()->GetNumber(); - iset[pl] = n->GetInfoset(); - int isetnum = iset[pl]->GetNumber(); - Gambit::Array snew(seq); - snew[pl] = 1; - for (i = 1; i < isetnum; i++) { - if (isetRow(pl, i)) { - snew[pl] += efsupp.NumActions(pl, i); - } - } - - (*(*E)[pl])(isetRow(pl, isetnum), seq[pl]) = (Gambit::Rational)1; - Sequence *myparent(parent[pl]); - - bool flag = false; - if (!isetFlag(pl, isetnum)) { // on first visit to iset, create new sequences - isetFlag(pl, isetnum) = 1; - flag = true; - } - for (i = 1; i <= n->NumChildren(); i++) { - if (efsupp.Contains(n->GetInfoset()->GetAction(i))) { - snew[pl] += 1; - if (flag) { - Sequence *child; - child = - new Sequence(n->GetPlayer(), n->GetInfoset()->GetAction(i), myparent, snew[pl]); - parent[pl] = child; - ((*sequences)[pl])->AddSequence(child); - } - - (*(*E)[pl])(isetRow(pl, isetnum), snew[pl]) = -(Gambit::Rational)1; - MakeSequenceForm(n->GetChild(i), prob, snew, iset, parent); - } - } + if (n->GetPlayer()->IsChance()) { + for (auto child : n->GetChildren()) { + BuildSequences(child, p_previousActions); } } -} - -void Sfg::GetSequenceDims(const Gambit::GameNode &n) -{ - int i; - - if (n->GetInfoset()) { - if (n->GetPlayer()->IsChance()) { - for (i = 1; i <= n->NumChildren(); i++) { - GetSequenceDims(n->GetChild(i)); - } + else { + auto &player_infoset_row = infoset_row[n->GetPlayer()]; + if (player_infoset_row.find(n->GetInfoset()) == player_infoset_row.end()) { + player_infoset_row[n->GetInfoset()] = player_infoset_row.size() + 1; } - else { - int pl = n->GetPlayer()->GetNumber(); - int isetnum = n->GetInfoset()->GetNumber(); - - bool flag = false; - if (!isetFlag(pl, isetnum)) { // on first visit to iset, create new sequences - infosets[pl].push_back(n->GetInfoset()); - isetFlag(pl, isetnum) = 1; - isetRow(pl, isetnum) = infosets[pl].Length() + 1; - flag = true; - } - for (i = 1; i <= n->NumChildren(); i++) { - if (efsupp.Contains(n->GetInfoset()->GetAction(i))) { - if (flag) { - seq[pl]++; - } - GetSequenceDims(n->GetChild(i)); - } - } + for (auto action : support.GetActions(n->GetInfoset())) { + m_actionParents[n->GetPlayer()][action] = p_previousActions[n->GetPlayer()]; + p_previousActions[n->GetPlayer()] = action; + BuildSequences(n->GetChild(action), p_previousActions); + p_previousActions[n->GetPlayer()] = m_actionParents[n->GetPlayer()][action]; } } } -int Sfg::TotalNumSequences() const +void Sfg::FillTableau(const GameNode &n, const Rational &prob, + PureSequenceProfile &previousActions) { - int tot = 0; - for (int i = 1; i <= seq.Length(); i++) { - tot += seq[i]; - } - return tot; -} - -int Sfg::NumPlayerInfosets() const -{ - int tot = 0; - for (int i = 1; i <= infosets.Length(); i++) { - tot += infosets[i].Length(); - } - return tot; -} - -int Sfg::InfosetRowNumber(int pl, int j) const -{ - if (j == 1) { - return 0; + if (n->GetOutcome()) { + for (auto player : support.GetGame()->GetPlayers()) { + GetMatrixEntry(previousActions, player) += + prob * static_cast(n->GetOutcome()->GetPayoff(player)); + } } - int isetnum = (*sequences)[pl]->Find(j)->GetInfoset()->GetNumber(); - return isetRow(pl, isetnum); -} - -int Sfg::ActionNumber(int pl, int j) const -{ - if (j == 1) { - return 0; + if (!n->GetInfoset()) { + return; } - return efsupp.GetIndex(GetAction(pl, j)); -} - -Gambit::GameInfoset Sfg::GetInfoset(int pl, int j) const -{ - if (j == 1) { - return nullptr; + if (n->GetPlayer()->IsChance()) { + for (auto action : n->GetInfoset()->GetActions()) { + FillTableau(n->GetChild(action), + prob * static_cast(n->GetInfoset()->GetActionProb(action)), + previousActions); + } } - return (*sequences)[pl]->Find(j)->GetInfoset(); -} - -Gambit::GameAction Sfg::GetAction(int pl, int j) const -{ - if (j == 1) { - return nullptr; + else { + GetConstraintEntry(n->GetInfoset(), previousActions.at(n->GetPlayer())) = Rational(1); + for (auto action : support.GetActions(n->GetInfoset())) { + if (m_sequenceColumns[n->GetPlayer()].find(action) == + m_sequenceColumns[n->GetPlayer()].end()) { + m_sequenceColumns[n->GetPlayer()][action] = m_sequenceColumns[n->GetPlayer()].size() + 1; + } + previousActions[n->GetPlayer()] = action; + GetConstraintEntry(n->GetInfoset(), action) = Rational(-1); + FillTableau(n->GetChild(action), prob, previousActions); + previousActions[n->GetPlayer()] = m_actionParents[n->GetPlayer()][action]; + } } - return (*sequences)[pl]->Find(j)->GetAction(); } -Gambit::MixedBehaviorProfile Sfg::ToBehav(const Gambit::PVector &x) const +MixedBehaviorProfile Sfg::ToBehav(const PVector &x) const { - Gambit::MixedBehaviorProfile b(efsupp); - - b = (Gambit::Rational)0; - - Sequence *sij; - const Sequence *parent; - Gambit::Rational value; - - int i, j; - for (i = 1; i <= EF->NumPlayers(); i++) { - for (j = 2; j <= seq[i]; j++) { - sij = ((*sequences)[i]->GetSFSequenceSet())[j]; - int sn = sij->GetNumber(); - parent = sij->Parent(); - - // gout << "\ni,j,sn,iset,act: " << i << " " << j << " " << sn << " "; - // gout << sij->GetInfoset()->GetNumber() << " " << sij->GetAction()->GetNumber(); - - if (x(i, parent->GetNumber()) > (double)0) { - value = Gambit::Rational(x(i, sn) / x(i, parent->GetNumber())); + MixedBehaviorProfile b(support); + b = 0; + + for (int i = 1; i <= support.GetGame()->NumPlayers(); i++) { + GamePlayer player = support.GetGame()->GetPlayer(i); + for (auto action_entry : m_sequenceColumns.at(player)) { + if (action_entry.first == nullptr) { + continue; } - else { - value = Gambit::Rational(0); + auto parent = m_actionParents.at(player).at(action_entry.first); + auto parent_prob = x(player->GetNumber(), m_sequenceColumns.at(player).at(parent)); + if (parent_prob > 0) { + b[action_entry.first] = x(player->GetNumber(), action_entry.second) / parent_prob; } - - b[sij->GetAction()] = value; } } return b; } -Gambit::Rational Sfg::Payoff(const Gambit::Array &index, int pl) const -{ - return Payoffs(index)[pl]; -} - -template class gNArray *>; +} // end namespace Gambit diff --git a/src/solvers/enumpoly/sfg.h b/src/solvers/enumpoly/sfg.h index 330be5f02..69d267ce6 100644 --- a/src/solvers/enumpoly/sfg.h +++ b/src/solvers/enumpoly/sfg.h @@ -23,52 +23,111 @@ #ifndef SFG_H #define SFG_H +#include + #include "gambit.h" #include "odometer.h" -#include "gnarray.h" -#include "sfstrat.h" +#include "ndarray.h" + +namespace Gambit { class Sfg { + using PureSequenceProfile = std::map; + private: - Gambit::Game EF; - const Gambit::BehaviorSupportProfile &efsupp; - Gambit::Array *sequences; - gNArray *> *SF; // sequence form - Gambit::Array *> - *E; // constraint matrices for sequence form. - Gambit::Array seq; - Gambit::PVector isetFlag, isetRow; - Gambit::Array> infosets; - - void MakeSequenceForm(const Gambit::GameNode &, const Gambit::Rational &, Gambit::Array, - Gambit::Array, Gambit::Array); - void GetSequenceDims(const Gambit::GameNode &); + BehaviorSupportProfile support; + std::map> m_sequenceColumns; + NDArray SF; // sequence form + std::map> E; // constraint matrices for sequence form. + std::map> infoset_row; + std::map> m_actionParents; + + void BuildSequences(const GameNode &, PureSequenceProfile &); + void FillTableau(const GameNode &, const Rational &, PureSequenceProfile &); + + Rational &GetMatrixEntry(const PureSequenceProfile &, const GamePlayer &); + Rational &GetConstraintEntry(const GameInfoset &, const GameAction &); public: - explicit Sfg(const Gambit::BehaviorSupportProfile &); - virtual ~Sfg(); + explicit Sfg(const BehaviorSupportProfile &); - inline int NumSequences(int pl) const { return seq[pl]; } - inline int NumInfosets(int pl) const { return infosets[pl].Length(); } - inline Gambit::Array NumSequences() const { return seq; } - int TotalNumSequences() const; - int NumPlayerInfosets() const; - inline int NumPlayers() const { return EF->NumPlayers(); } + ~Sfg() = default; - inline Gambit::Array Payoffs(const Gambit::Array &index) const + int NumSequences(const GamePlayer &p_player) const { - return *((*SF)[index]); + return m_sequenceColumns.at(p_player).size(); } - Gambit::Rational Payoff(const Gambit::Array &index, int pl) const; - - Gambit::RectArray Constraints(int player) const { return *((*E)[player]); }; - int InfosetRowNumber(int pl, int sequence) const; - int ActionNumber(int pl, int sequence) const; - Gambit::GameInfoset GetInfoset(int pl, int sequence) const; - Gambit::GameAction GetAction(int pl, int sequence) const; - const Gambit::Game &GetEfg() const { return EF; } - Gambit::MixedBehaviorProfile ToBehav(const Gambit::PVector &x) const; - const Sequence *GetSequence(int pl, int seq) const { return ((*sequences)[pl])->Find(seq); } + + int NumInfosets(const GamePlayer &p_player) const { return infoset_row.at(p_player).size(); } + + const Array &NumSequences() const { return SF.GetIndexDimension(); } + + int TotalNumSequences() const + { + auto &dim = SF.GetIndexDimension(); + return std::accumulate(dim.cbegin(), dim.cend(), 0); + } + + int NumPlayerInfosets() const + { + auto players = support.GetGame()->GetPlayers(); + return std::accumulate( + players.cbegin(), players.cend(), 0, + [this](int accum, const GamePlayer &player) { return accum + NumInfosets(player); }); + } + + int NumPlayers() const { return support.GetGame()->NumPlayers(); } + + const Rational &GetPayoff(const Array &index, int pl) const { return SF.at(index, pl); } + + const Rational &GetConstraintEntry(const GamePlayer &p_player, int infoset_row, + int seq_col) const + { + return E.at(p_player)(infoset_row, seq_col); + } + + int InfosetRowNumber(const GamePlayer &p_player, int sequence) const + { + if (sequence == 1) { + return 0; + } + for (auto entry : m_sequenceColumns.at(p_player)) { + if (entry.second == sequence) { + return infoset_row.at(p_player).at(entry.first->GetInfoset()); + } + } + return 0; + } + + int ActionNumber(const GamePlayer &p_player, int sequence) const + { + if (sequence == 1) { + return 0; + } + for (auto entry : m_sequenceColumns.at(p_player)) { + if (entry.second == sequence) { + return support.GetIndex(entry.first); + } + } + return 0; + } + + GameInfoset GetInfoset(const GamePlayer &p_player, int sequence) const + { + if (sequence == 1) { + return 0; + } + for (auto entry : m_sequenceColumns.at(p_player)) { + if (entry.second == sequence) { + return entry.first->GetInfoset(); + } + } + return 0; + } + + MixedBehaviorProfile ToBehav(const PVector &x) const; }; +} // end namespace Gambit + #endif // SFG_H diff --git a/src/solvers/enumpoly/sfstrat.cc b/src/solvers/enumpoly/sfstrat.cc deleted file mode 100644 index fa3e75351..000000000 --- a/src/solvers/enumpoly/sfstrat.cc +++ /dev/null @@ -1,249 +0,0 @@ -// -// This file is part of Gambit -// Copyright (c) 1994-2024, The Gambit Project (http://www.gambit-project.org) -// -// FILE: src/tools/enumpoly/sfstrat.cc -// Implementation of sequence form strategy classes -// -// This program is free software; you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation; either version 2 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -// - -#include "sfstrat.h" -#include "sfg.h" - -//-------------------------------------- -// Sequence: Member functions -//-------------------------------------- - -Gambit::List Sequence::History() const -{ - Gambit::List h; - Gambit::GameAction a = action; - const Sequence *s = (this); - while (a) { - h.push_back(a); - s = s->parent; - a = s->GetAction(); - } - return h; -} - -//-------------------------------------- -// SFSequenceSet: Member functions -//-------------------------------------- - -SFSequenceSet::SFSequenceSet(const Gambit::GamePlayer &p) : efp(p), sequences() -{ - Sequence *empty; - empty = new Sequence(p, nullptr, nullptr, 1); - AddSequence(empty); -} - -SFSequenceSet::SFSequenceSet(const SFSequenceSet &s) - - = default; - -SFSequenceSet::~SFSequenceSet() -{ - - // potential problem here? It is not clear this is where this belongs. - // What if there are multiple SFSequenceSets pointing to - // the same sequences? - - for (int i = 1; i <= sequences.Length(); i++) { - delete sequences[i]; - } -} - -SFSequenceSet &SFSequenceSet::operator=(const SFSequenceSet &s) -{ - if (this != &s) { - efp = s.efp; - sequences = s.sequences; - } - return *this; -} - -bool SFSequenceSet::operator==(const SFSequenceSet &s) -{ - if (sequences.Length() != s.sequences.Length()) { - return (false); - } - int i; - for (i = 1; i <= sequences.Length() && sequences[i] == s.sequences[i]; i++) - ; - if (i > sequences.Length()) { - return (true); - } - else { - return (false); - } -} - -//------------------------------------------ -// SFSequenceSet: Member functions -//------------------------------------------ - -// Append a sequences to the SFSequenceSet -void SFSequenceSet::AddSequence(Sequence *s) -{ - if (efp != s->Player()) { - throw Gambit::MismatchException(); - } - sequences.push_back(s); -} - -// Removes a sequence pointer. Returns true if the sequence was successfully -// removed, false otherwise. -bool SFSequenceSet::RemoveSequence(Sequence *s) -{ - if (efp != s->Player()) { - throw Gambit::MismatchException(); - } - int t; - t = sequences.Find(s); - if (t > 0) { - sequences.Remove(t); - } - return (t > 0); -} - -// Finds the sequence pointer of sequence number j. Returns 0 if there -// is no sequence with that number. -Sequence *SFSequenceSet::Find(int j) -{ - int t = 1; - while (t <= sequences.Length()) { - if (sequences[t]->GetNumber() == j) { - return sequences[t]; - } - t++; - } - return nullptr; -} - -// Number of Sequences in a SFSequenceSet -int SFSequenceSet::NumSequences() const { return (sequences.Length()); } - -// Return the entire sequence set -const Gambit::Array &SFSequenceSet::GetSFSequenceSet() const { return sequences; } - -//----------------------------------------------- -// SFSupport: Ctors, Dtor, Operators -//----------------------------------------------- - -SFSupport::SFSupport(const Sfg &SF) : bsfg(&SF), sups(SF.GetEfg()->NumPlayers()) -{ - for (int i = 1; i <= sups.Length(); i++) { - sups[i] = new SFSequenceSet(SF.GetEfg()->GetPlayer(i)); - } -} - -SFSupport::SFSupport(const SFSupport &s) : bsfg(s.bsfg), sups(s.sups.Length()) -{ - for (int i = 1; i <= sups.Length(); i++) { - sups[i] = new SFSequenceSet(*s.sups[i]); - } -} - -SFSupport::~SFSupport() -{ - for (int i = 1; i <= sups.Length(); i++) { - delete sups[i]; - } -} - -SFSupport &SFSupport::operator=(const SFSupport &s) -{ - if (this != &s && bsfg == s.bsfg) { - for (int i = 1; i <= sups.Length(); i++) { - delete sups[i]; - sups[i] = new SFSequenceSet(*s.sups[i]); - } - } - return *this; -} - -bool SFSupport::operator==(const SFSupport &s) const -{ - int i; - for (i = 1; i <= sups.Length() && *sups[i] == *s.sups[i]; i++) - ; - return i > sups.Length(); -} - -bool SFSupport::operator!=(const SFSupport &s) const { return !(*this == s); } - -//------------------------ -// SFSupport: Members -//------------------------ - -const Gambit::Array &SFSupport::Sequences(int pl) const -{ - return (sups[pl]->GetSFSequenceSet()); -} - -int SFSupport::NumSequences(int pl) const { return sups[pl]->NumSequences(); } - -Gambit::Array SFSupport::NumSequences() const -{ - Gambit::Array a(sups.Length()); - - for (int i = 1; i <= a.Length(); i++) { - a[i] = sups[i]->NumSequences(); - } - return a; -} - -int SFSupport::TotalNumSequences() const -{ - int total = 0; - for (int i = 1; i <= sups.Length(); i++) { - total += sups[i]->NumSequences(); - } - return total; -} - -int SFSupport::Find(Sequence *s) const -{ - return sups[s->Player()->GetNumber()]->GetSFSequenceSet().Find(s); -} - -void SFSupport::AddSequence(Sequence *s) { sups[s->Player()->GetNumber()]->AddSequence(s); } - -bool SFSupport::RemoveSequence(Sequence *s) -{ - return sups[s->Player()->GetNumber()]->RemoveSequence(s); -} - -// Returns true if all sequences in _THIS_ belong to _S_ -bool SFSupport::IsSubset(const SFSupport &s) const -{ - for (int i = 1; i <= sups.Length(); i++) { - if (NumSequences(i) > s.NumSequences(i)) { - return false; - } - else { - const Gambit::Array &strats = sups[i]->GetSFSequenceSet(); - - for (int j = 1; j <= NumSequences(i); j++) { - if (!s.sups[i]->GetSFSequenceSet().Find(strats[j])) { - return false; - } - } - } - } - return true; -} diff --git a/src/solvers/enumpoly/sfstrat.h b/src/solvers/enumpoly/sfstrat.h deleted file mode 100644 index c506d7697..000000000 --- a/src/solvers/enumpoly/sfstrat.h +++ /dev/null @@ -1,150 +0,0 @@ -// -// This file is part of Gambit -// Copyright (c) 1994-2024, The Gambit Project (http://www.gambit-project.org) -// -// FILE: src/tools/enumpoly/sfstrat.h -// Interface to sequence form strategy classes -// -// This program is free software; you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation; either version 2 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -// - -#ifndef SFSTRAT_H -#define SFSTRAT_H - -#include "gambit.h" - -struct Sequence { - friend class Sfg; - friend class SFSequenceSet; - -private: - int number; - std::string name; - Gambit::GamePlayer player; - Gambit::GameAction action; - const Sequence *parent; - - Sequence(const Gambit::GamePlayer &pl, const Gambit::GameAction &a, const Sequence *p, int n) - : number(n), player(pl), action(a), parent(p) - { - } - ~Sequence() = default; - -public: - const std::string &GetName() const { return name; } - void SetName(const std::string &s) { name = s; } - - Gambit::List History() const; - int GetNumber() const { return number; } - Gambit::GameAction GetAction() const { return action; } - Gambit::GameInfoset GetInfoset() const - { - if (action) { - return action->GetInfoset(); - } - return nullptr; - } - Gambit::GamePlayer Player() const { return player; } - const Sequence *Parent() const { return parent; } -}; - -class SFSequenceSet { -protected: - Gambit::GamePlayer efp; - Gambit::Array sequences; - -public: - SFSequenceSet(const SFSequenceSet &s); - explicit SFSequenceSet(const Gambit::GamePlayer &p); - - SFSequenceSet &operator=(const SFSequenceSet &s); - bool operator==(const SFSequenceSet &s); - - virtual ~SFSequenceSet(); - - // Append a sequence to the SFSequenceSet - void AddSequence(Sequence *s); - - // Removes a sequence pointer. Returns true if the sequence was successfully - // removed, false otherwise. - bool RemoveSequence(Sequence *s); - Sequence *Find(int j); - - // Number of sequences in the SFSequenceSet - int NumSequences() const; - - // return the entire sequence set in a const Gambit::Array - const Gambit::Array &GetSFSequenceSet() const; -}; - -class Sfg; - -class SFSupport { -protected: - const Sfg *bsfg; - Gambit::Array sups; - -public: - explicit SFSupport(const Sfg &); - SFSupport(const SFSupport &s); - virtual ~SFSupport(); - SFSupport &operator=(const SFSupport &s); - - bool operator==(const SFSupport &s) const; - bool operator!=(const SFSupport &s) const; - - const Sfg &Game() const { return *bsfg; } - - const Gambit::Array &Sequences(int pl) const; - - int NumSequences(int pl) const; - Gambit::Array NumSequences() const; - int TotalNumSequences() const; - - void AddSequence(Sequence *); - bool RemoveSequence(Sequence *); - - bool IsSubset(const SFSupport &s) const; - - // returns the index of the sequence in the support if it exists, - // otherwise returns zero - int Find(Sequence *) const; -}; - -class SequenceProfile { - friend class Sfg; - -private: - long index; - Gambit::Array profile; - -public: - explicit SequenceProfile(const Sfg &); - SequenceProfile(const SequenceProfile &p); - - ~SequenceProfile(); - - SequenceProfile &operator=(const SequenceProfile &); - - bool IsValid() const; - - long GetIndex() const; - - Sequence *const operator[](int p) const; - Sequence *const Get(int p) const; - void Set(int p, Sequence *const s); -}; - -#endif // SFSTRAT_H From c2c77edd42712773ed3181eae8e1e75ce39d0ec8 Mon Sep 17 00:00:00 2001 From: Ted Turocy Date: Wed, 18 Sep 2024 11:40:18 +0100 Subject: [PATCH 14/99] Modernise further the implementation of enumpoly on sequence forms This builds on previous work to bring the implementation of enumpoly on extensive forms (via the sequence form) up-to-date. This is a substantial refactoring that should improve the readability and maintainability of these routines. Further, the `GameSequenceForm` class has now moved towards being ready to move to be part of the game representation library, which the goal is to do in due course. --- Makefile.am | 4 +- src/games/behavmixed.cc | 15 ++ src/games/behavmixed.h | 2 + src/games/game.cc | 13 +- src/solvers/enumpoly/efgpoly.cc | 299 ++++++++++--------------------- src/solvers/enumpoly/gameseq.cc | 132 ++++++++++++++ src/solvers/enumpoly/gameseq.h | 304 ++++++++++++++++++++++++++++++++ src/solvers/enumpoly/sfg.cc | 153 ---------------- src/solvers/enumpoly/sfg.h | 133 -------------- 9 files changed, 554 insertions(+), 501 deletions(-) create mode 100644 src/solvers/enumpoly/gameseq.cc create mode 100644 src/solvers/enumpoly/gameseq.h delete mode 100644 src/solvers/enumpoly/sfg.cc delete mode 100644 src/solvers/enumpoly/sfg.h diff --git a/Makefile.am b/Makefile.am index 1b1cf5369..70a2a4ad3 100644 --- a/Makefile.am +++ b/Makefile.am @@ -421,6 +421,8 @@ gambit_nashsupport_SOURCES = \ # For enumpoly, sources starting in 'pel' are from Pelican. gambit_enumpoly_SOURCES = \ ${core_SOURCES} ${game_SOURCES} ${gambit_nashsupport_SOURCES} \ + src/solvers/enumpoly/gameseq.cc \ + src/solvers/enumpoly/gameseq.h \ src/solvers/enumpoly/gpartltr.cc \ src/solvers/enumpoly/gpartltr.h \ src/solvers/enumpoly/gpartltr.imp \ @@ -464,8 +466,6 @@ gambit_enumpoly_SOURCES = \ src/solvers/enumpoly/linrcomb.h \ src/solvers/enumpoly/linrcomb.imp \ src/solvers/enumpoly/ndarray.h \ - src/solvers/enumpoly/sfg.cc \ - src/solvers/enumpoly/sfg.h \ src/solvers/enumpoly/odometer.cc \ src/solvers/enumpoly/odometer.h \ src/solvers/enumpoly/nfghs.cc \ diff --git a/src/games/behavmixed.cc b/src/games/behavmixed.cc index 711fb90e3..06b742d8e 100644 --- a/src/games/behavmixed.cc +++ b/src/games/behavmixed.cc @@ -223,6 +223,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 //======================================================================== diff --git a/src/games/behavmixed.h b/src/games/behavmixed.h index bccb4fe19..d72394fe9 100644 --- a/src/games/behavmixed.h +++ b/src/games/behavmixed.h @@ -212,6 +212,8 @@ template class MixedBehaviorProfile { MixedStrategyProfile ToMixedProfile() const; + /// @brief Converts the profile to one on the full support of the game + MixedBehaviorProfile ToFullSupport() const; //@} }; diff --git a/src/games/game.cc b/src/games/game.cc index 0440d0012..1bd60a4e5 100644 --- a/src/games/game.cc +++ b/src/games/game.cc @@ -466,18 +466,11 @@ 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 (auto player : m_rep->m_support.GetGame()->GetPlayers()) { + for (auto strategy : player->GetStrategies()) { + full[strategy] = (m_rep->m_support.Contains(strategy)) ? (*this)[strategy] : T(0); } } - return full; } diff --git a/src/solvers/enumpoly/efgpoly.cc b/src/solvers/enumpoly/efgpoly.cc index e7a09689f..82a10e0c9 100644 --- a/src/solvers/enumpoly/efgpoly.cc +++ b/src/solvers/enumpoly/efgpoly.cc @@ -24,7 +24,7 @@ #include "enumpoly.h" #include "solvers/nashsupport/nashsupport.h" -#include "sfg.h" +#include "gameseq.h" #include "gpoly.h" #include "gpolylst.h" #include "rectangl.h" @@ -33,204 +33,125 @@ using namespace Gambit; +namespace { + +// The set of polynomials is constructed incorporating these techniques: // -// A class to organize the data needed to build the polynomials -// +// 1. The sum-to-one equations and sequence form constraints are substituted +// into the system, resuting in a reduction of the number of variables. +// This is accomplished by BuildSequenceVariable(), resulting in a +// mapping from each sequence to an expression - which may be a single +// variable for the probability of that sequence, or an expression of +// that probability in terms of the probabilities of other sequences. +// 2. The indifference conditions are implemented by computing the +// expected payoff polynomial for a player, and then taking the +// partial derivative of that with respect to each of the player's +// sequence probabilities (after the substitutions mentioned above) +// and setting those to zero. + class ProblemData { public: - const BehaviorSupportProfile &support; - Sfg SF; - int nVars; - gSpace *Space; - term_order *Lex; - Array> var; + GameSequenceForm sfg; + gSpace Space; + term_order Lex; + std::map var; + std::map> variables; explicit ProblemData(const BehaviorSupportProfile &p_support); - ~ProblemData(); }; -ProblemData::ProblemData(const BehaviorSupportProfile &p_support) - : support(p_support), SF(p_support), - nVars(SF.TotalNumSequences() - SF.NumPlayerInfosets() - SF.NumPlayers()), - Space(new gSpace(nVars)), Lex(new term_order(Space, lex)), - var(p_support.GetGame()->NumPlayers()) -{ - int tnv = 0; - for (int pl = 1; pl <= p_support.GetGame()->NumPlayers(); pl++) { - GamePlayer player = p_support.GetGame()->GetPlayer(pl); - var[pl] = Array(SF.NumSequences(player)); - var[pl][1] = 0; - for (int seq = 2; seq <= SF.NumSequences(player); seq++) { - int act = SF.ActionNumber(player, seq); - GameInfoset infoset = SF.GetInfoset(player, seq); - if (act < p_support.NumActions(infoset)) { - var[pl][seq] = ++tnv; - } - else { - var[pl][seq] = 0; - } - } - } -} - -ProblemData::~ProblemData() -{ - delete Lex; - delete Space; -} - -//======================================================================= -// Constructing the equilibrium conditions -//======================================================================= - -// The strategy is to develop the polynomial for each agent's expected -// payoff as a function of the behavior strategies on the support, -// eliminating the last action probability for each information set. -// The system is obtained by requiring that all the partial -// derivatives vanish, and that the sum of action probabilities at -// each information set be less than one. - -gPoly ProbOfSequence(const ProblemData &p_data, const GamePlayer &p_player, int seq) +gPoly BuildSequenceVariable(ProblemData &p_data, const GameSequence &p_sequence, + const std::map &var) { - gPoly equation(p_data.Space, p_data.Lex); - Vector exps(p_data.nVars); - - GameInfoset infoset = p_data.SF.GetInfoset(p_player, seq); - - if (seq == 1) { - exps = 0; - exp_vect const_exp(p_data.Space, exps); - gMono const_term(1.0, const_exp); - gPoly new_term(p_data.Space, const_term, p_data.Lex); - equation += new_term; + if (!p_sequence->action) { + return {&p_data.Space, 1, &p_data.Lex}; } - else if (p_data.SF.ActionNumber(p_player, seq) < p_data.support.NumActions(infoset)) { - exps = 0; - exps[p_data.var[p_player->GetNumber()][seq]] = 1; - exp_vect const_exp(p_data.Space, exps); - gMono const_term(1.0, const_exp); - gPoly new_term(p_data.Space, const_term, p_data.Lex); - equation += new_term; - } - else { - int isetrow = p_data.SF.InfosetRowNumber(p_player, seq); - for (int j = 1; j < seq; j++) { - if (p_data.SF.GetConstraintEntry(p_player, isetrow, j) == Rational(-1)) { - equation -= ProbOfSequence(p_data, p_player, j); - } - else if (p_data.SF.GetConstraintEntry(p_player, isetrow, j) == Rational(1)) { - equation += ProbOfSequence(p_data, p_player, j); - } - } + if (p_data.sfg.GetSupport().GetIndex(p_sequence->action) < + p_data.sfg.GetSupport().NumActions(p_sequence->GetInfoset())) { + return {&p_data.Space, var.at(p_sequence), 1, &p_data.Lex}; } - return equation; -} -gPoly GetPayoff(const ProblemData &p_data, int pl) -{ - gIndexOdometer index(p_data.SF.NumSequences()); - - gPoly equation(p_data.Space, p_data.Lex); - while (index.Turn()) { - auto pay = p_data.SF.GetPayoff(index.CurrentIndices(), pl); - if (pay != Rational(0)) { - gPoly term(p_data.Space, double(pay), p_data.Lex); - for (int k = 1; k <= p_data.support.GetGame()->NumPlayers(); k++) { - term *= ProbOfSequence(p_data, p_data.support.GetGame()->GetPlayer(k), - index.CurrentIndices()[k]); - } - equation += term; + gPoly equation(&p_data.Space, &p_data.Lex); + for (auto seq : p_data.sfg.GetSequences(p_sequence->player)) { + if (seq == p_sequence) { + continue; + } + if (int constraint_coef = + p_data.sfg.GetConstraintEntry(p_sequence->GetInfoset(), seq->action)) { + equation += double(constraint_coef) * BuildSequenceVariable(p_data, seq, var); } } return equation; } -gPolyList IndifferenceEquations(const ProblemData &p_data) +ProblemData::ProblemData(const BehaviorSupportProfile &p_support) + : sfg(p_support), + Space(sfg.GetSequences().size() - sfg.GetInfosets().size() - sfg.GetPlayers().size()), + Lex(&Space, lex) { - gPolyList equations(p_data.Space, p_data.Lex); - - int kk = 0; - for (int pl = 1; pl <= p_data.SF.NumPlayers(); pl++) { - GamePlayer player = p_data.support.GetGame()->GetPlayer(pl); - gPoly payoff = GetPayoff(p_data, pl); - int n_vars = p_data.SF.NumSequences(player) - p_data.SF.NumInfosets(player) - 1; - for (int j = 1; j <= n_vars; j++) { - equations += payoff.PartialDerivative(kk + j); + for (auto sequence : sfg.GetSequences()) { + if (sequence->action && + (p_support.GetIndex(sequence->action) < p_support.NumActions(sequence->GetInfoset()))) { + var[sequence] = var.size() + 1; } - kk += n_vars; } - return equations; + for (auto sequence : sfg.GetSequences()) { + variables.emplace(sequence, BuildSequenceVariable(*this, sequence, var)); + } } -gPolyList LastActionProbPositiveInequalities(const ProblemData &p_data) +gPoly GetPayoff(ProblemData &p_data, const GamePlayer &p_player) { - gPolyList equations(p_data.Space, p_data.Lex); + gPoly equation(&p_data.Space, &p_data.Lex); - for (int i = 1; i <= p_data.SF.NumPlayers(); i++) { - GamePlayer player = p_data.support.GetGame()->GetPlayer(i); - for (int j = 2; j <= p_data.SF.NumSequences(player); j++) { - int act_num = p_data.SF.ActionNumber(player, j); - GameInfoset infoset = p_data.SF.GetInfoset(player, j); - if (act_num == p_data.support.NumActions(infoset) && act_num > 1) { - equations += ProbOfSequence(p_data, player, j); + for (auto profile : p_data.sfg.GetContingencies()) { + auto pay = p_data.sfg.GetPayoff(profile, p_player); + if (pay != Rational(0)) { + gPoly term(&p_data.Space, double(pay), &p_data.Lex); + for (auto player : p_data.sfg.GetPlayers()) { + term *= p_data.variables.at(profile[player]); } + equation += term; } } - - return equations; -} - -gPolyList NashOnSupportEquationsAndInequalities(const ProblemData &p_data) -{ - gPolyList equations(p_data.Space, p_data.Lex); - - equations += IndifferenceEquations(p_data); - equations += LastActionProbPositiveInequalities(p_data); - - return equations; + return equation; } -//======================================================================= -// Mapping solution vectors to sequences -//======================================================================= - -double NumProbOfSequence(const ProblemData &p_data, const GamePlayer &p_player, int seq, - const Vector &x) +void IndifferenceEquations(ProblemData &p_data, gPolyList &p_equations) { - GameInfoset infoset = p_data.SF.GetInfoset(p_player, seq); - - if (seq == 1) { - return 1.0; - } - else if (p_data.SF.ActionNumber(p_player, seq) < p_data.support.NumActions(infoset)) { - return x[p_data.var[p_player->GetNumber()][seq]]; - } - else { - double value = 0.0; - int isetrow = p_data.SF.InfosetRowNumber(p_player, seq); - for (int j = 1; j < seq; j++) { - if (p_data.SF.GetConstraintEntry(p_player, isetrow, j) == Rational(-1)) { - value -= NumProbOfSequence(p_data, p_player, j, x); + for (auto player : p_data.sfg.GetPlayers()) { + gPoly payoff = GetPayoff(p_data, player); + for (auto sequence : p_data.sfg.GetSequences(player)) { + try { + p_equations += payoff.PartialDerivative(p_data.var.at(sequence)); } - else if (p_data.SF.GetConstraintEntry(p_player, isetrow, j) == Rational(1)) { - value += NumProbOfSequence(p_data, p_player, j, x); + catch (std::out_of_range) { + // This sequence's variable was already substituted out in terms of + // the probabilities of other sequences } } - return value; } } -PVector SeqFormVectorFromSolFormVector(const ProblemData &p_data, const Vector &v) +void LastActionProbPositiveInequalities(ProblemData &p_data, gPolyList &p_equations) { - PVector x(p_data.SF.NumSequences()); - - for (int i = 1; i <= p_data.support.GetGame()->NumPlayers(); i++) { - for (int j = 1; j <= p_data.SF.NumSequences()[i]; j++) { - x(i, j) = NumProbOfSequence(p_data, p_data.support.GetGame()->GetPlayer(i), j, v); + for (auto sequence : p_data.sfg.GetSequences()) { + int action_number = + (sequence->action) ? p_data.sfg.GetSupport().GetIndex(sequence->action) : 0; + if (action_number > 1 && + action_number == p_data.sfg.GetSupport().NumActions(sequence->action->GetInfoset())) { + p_equations += p_data.variables.at(sequence); } } +} +std::map ToSequenceProbs(const ProblemData &p_data, const Vector &v) +{ + std::map x; + for (auto sequence : p_data.sfg.GetSequences()) { + x[sequence] = p_data.variables.at(sequence).Evaluate(v); + } return x; } @@ -241,67 +162,43 @@ bool ExtendsToNash(const MixedBehaviorProfile &bs) BehaviorSupportProfile(bs.GetGame())); } -List> SolveSupport(const BehaviorSupportProfile &p_support, - bool &p_isSingular) +std::list> SolveSupport(const BehaviorSupportProfile &p_support, + bool &p_isSingular) { ProblemData data(p_support); - gPolyList equations = NashOnSupportEquationsAndInequalities(data); + gPolyList equations(&data.Space, &data.Lex); + IndifferenceEquations(data, equations); + LastActionProbPositiveInequalities(data, equations); // set up the rectangle of search - Vector bottoms(data.nVars), tops(data.nVars); + Vector bottoms(data.Space.Dmnsn()), tops(data.Space.Dmnsn()); bottoms = 0; tops = 1; - gRectangle Cube(bottoms, tops); QuikSolv quickie(equations); try { - quickie.FindCertainNumberOfRoots(Cube, std::numeric_limits::max(), 0); + quickie.FindCertainNumberOfRoots(gRectangle(bottoms, tops), + std::numeric_limits::max(), 0); } - catch (const Gambit::SingularMatrixException &) { + catch (const SingularMatrixException &) { p_isSingular = true; } - catch (const Gambit::AssertionException &e) { + catch (const AssertionException &e) { // std::cerr << "Assertion warning: " << e.what() << std::endl; p_isSingular = true; } - List> solutionlist = quickie.RootList(); - - List> solutions; - for (int k = 1; k <= solutionlist.Length(); k++) { - PVector y = SeqFormVectorFromSolFormVector(data, solutionlist[k]); - MixedBehaviorProfile sol(data.SF.ToBehav(y)); + std::list> solutions; + for (auto root : quickie.RootList()) { + MixedBehaviorProfile sol(data.sfg.ToMixedBehaviorProfile(ToSequenceProbs(data, root))); if (ExtendsToNash(sol)) { solutions.push_back(sol); } } - return solutions; } -MixedBehaviorProfile ToFullSupport(const MixedBehaviorProfile &p_profile) -{ - Game efg = p_profile.GetGame(); - const BehaviorSupportProfile &support = p_profile.GetSupport(); - - MixedBehaviorProfile fullProfile(efg); - fullProfile = 0.0; - - int index = 1; - for (int pl = 1; pl <= efg->NumPlayers(); pl++) { - GamePlayer player = efg->GetPlayer(pl); - for (int iset = 1; iset <= player->NumInfosets(); iset++) { - GameInfoset infoset = player->GetInfoset(iset); - for (int act = 1; act <= infoset->NumActions(); act++) { - if (support.Contains(infoset->GetAction(act))) { - fullProfile[infoset->GetAction(act)] = p_profile[index++]; - } - } - } - } - - return fullProfile; -} +} // end anonymous namespace namespace Gambit { namespace Nash { @@ -316,18 +213,14 @@ EnumPolyBehaviorSolve(const Game &p_game, for (auto support : possible_supports->m_supports) { p_onSupport("candidate", support); - bool isSingular = false; - List> newsolns = SolveSupport(support, isSingular); - - for (int j = 1; j <= newsolns.Length(); j++) { - MixedBehaviorProfile fullProfile = ToFullSupport(newsolns[j]); + for (auto solution : SolveSupport(support, isSingular)) { + MixedBehaviorProfile fullProfile = solution.ToFullSupport(); if (fullProfile.GetLiapValue() < 1.0e-6) { p_onEquilibrium(fullProfile); ret.push_back(fullProfile); } } - if (isSingular) { p_onSupport("singular", support); } diff --git a/src/solvers/enumpoly/gameseq.cc b/src/solvers/enumpoly/gameseq.cc new file mode 100644 index 000000000..34f8064ac --- /dev/null +++ b/src/solvers/enumpoly/gameseq.cc @@ -0,0 +1,132 @@ +// +// This file is part of Gambit +// Copyright (c) 1994-2024, The Gambit Project (http://www.gambit-project.org) +// +// FILE: src/tools/enumpoly/sfg.cc +// Implementation of sequence form classes +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// + +#include "gambit.h" +#include "gameseq.h" + +using namespace Gambit; + +namespace Gambit { + +void GameSequenceForm::BuildSequences(const GameNode &n, + std::map &p_currentSequences) +{ + if (!n->GetInfoset()) { + return; + } + if (n->GetPlayer()->IsChance()) { + for (auto child : n->GetChildren()) { + BuildSequences(child, p_currentSequences); + } + } + else { + m_infosets.insert(n->GetInfoset()); + auto tmp_sequence = p_currentSequences.at(n->GetPlayer()); + for (auto action : m_support.GetActions(n->GetInfoset())) { + if (m_correspondence.find(action) == m_correspondence.end()) { + m_sequences[n->GetPlayer()].push_back(std::make_shared( + n->GetPlayer(), action, m_sequences[n->GetPlayer()].size() + 1, tmp_sequence)); + m_correspondence[action] = m_sequences[n->GetPlayer()].back(); + } + p_currentSequences[n->GetPlayer()] = m_correspondence[action]; + BuildSequences(n->GetChild(action), p_currentSequences); + } + p_currentSequences[n->GetPlayer()] = tmp_sequence; + } +} + +void GameSequenceForm::BuildSequences() +{ + std::map currentSequences; + for (auto player : GetPlayers()) { + m_sequences[player] = { + std::make_shared(player, nullptr, 1, std::weak_ptr())}; + currentSequences[player] = m_sequences[player].front(); + } + BuildSequences(m_support.GetGame()->GetRoot(), currentSequences); +} + +void GameSequenceForm::FillTableau(const GameNode &n, const Rational &prob, + std::map &p_currentSequences) +{ + if (n->GetOutcome()) { + for (auto player : m_support.GetGame()->GetPlayers()) { + GetPayoffEntry(p_currentSequences, player) += + prob * static_cast(n->GetOutcome()->GetPayoff(player)); + } + } + if (!n->GetInfoset()) { + return; + } + if (n->GetPlayer()->IsChance()) { + for (auto action : n->GetInfoset()->GetActions()) { + FillTableau(n->GetChild(action), + prob * static_cast(n->GetInfoset()->GetActionProb(action)), + p_currentSequences); + } + } + else { + auto tmp_sequence = p_currentSequences.at(n->GetPlayer()); + m_constraints[{n->GetInfoset(), p_currentSequences.at(n->GetPlayer())->action}] = 1; + for (auto action : m_support.GetActions(n->GetInfoset())) { + m_constraints[{n->GetInfoset(), action}] = -1; + p_currentSequences[n->GetPlayer()] = m_correspondence.at(action); + FillTableau(n->GetChild(action), prob, p_currentSequences); + } + p_currentSequences[n->GetPlayer()] = tmp_sequence; + } +} + +void GameSequenceForm::FillTableau() +{ + Array dim(m_sequences.size()); + for (auto player : GetPlayers()) { + dim[player->GetNumber()] = m_sequences.at(player).size(); + } + m_payoffs = NDArray(dim, dim.size()); + + std::map currentSequence; + for (auto player : GetPlayers()) { + currentSequence[player] = m_sequences[player].front(); + } + FillTableau(m_support.GetGame()->GetRoot(), Rational(1), currentSequence); +} + +MixedBehaviorProfile +GameSequenceForm::ToMixedBehaviorProfile(const std::map &x) const +{ + MixedBehaviorProfile b(m_support); + for (auto sequence : GetSequences()) { + if (sequence->action == nullptr) { + continue; + } + if (double parent_prob = x.at(sequence->parent.lock()) > 0) { + b[sequence->action] = x.at(sequence) / parent_prob; + } + else { + b[sequence->action] = 0; + } + } + return b; +} + +} // end namespace Gambit diff --git a/src/solvers/enumpoly/gameseq.h b/src/solvers/enumpoly/gameseq.h new file mode 100644 index 000000000..ca2656ac6 --- /dev/null +++ b/src/solvers/enumpoly/gameseq.h @@ -0,0 +1,304 @@ +// +// This file is part of Gambit +// Copyright (c) 1994-2024, The Gambit Project (http://www.gambit-project.org) +// +// FILE: src/tools/enumpoly/sfg.h +// Interface to sequence form classes +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// + +#ifndef SFG_H +#define SFG_H + +#include + +#include "gambit.h" +#include "odometer.h" +#include "ndarray.h" + +namespace Gambit { + +struct GameSequenceRep { +public: + GamePlayer player; + GameAction action; + size_t number; + std::weak_ptr parent; + + explicit GameSequenceRep(const GamePlayer &p_player, const GameAction &p_action, size_t p_number, + std::weak_ptr p_parent) + : player(p_player), action(p_action), number(p_number), parent(p_parent) + { + } + + GameInfoset GetInfoset(void) const { return (action) ? action->GetInfoset() : nullptr; } + + bool operator<(const GameSequenceRep &other) const + { + return player < other.player || (player == other.player && action < other.action); + } + bool operator==(const GameSequenceRep &other) const + { + return player == other.player && action == other.action; + } +}; + +using GameSequence = std::shared_ptr; + +class GameSequenceForm { +private: + BehaviorSupportProfile m_support; + std::map> m_sequences; + NDArray m_payoffs; + std::map, int> m_constraints; // (sparse) constraint matrices + std::set m_infosets; // infosets actually reachable given support + std::map m_correspondence; + + void BuildSequences(); + void BuildSequences(const GameNode &, std::map &); + void FillTableau(); + void FillTableau(const GameNode &, const Rational &, std::map &); + + Array ProfileToIndex(const std::map &p_profile) const + { + Array index(p_profile.size()); + for (auto player : GetPlayers()) { + index[player->GetNumber()] = p_profile.at(player)->number; + } + return index; + } + + Rational &GetPayoffEntry(const std::map &p_profile, + const GamePlayer &p_player) + { + return m_payoffs.at(ProfileToIndex(p_profile), p_player->GetNumber()); + } + +public: + class Infosets { + private: + const GameSequenceForm *m_sfg; + + public: + Infosets(const GameSequenceForm *p_sfg) : m_sfg(p_sfg) {} + + size_t size() const { return m_sfg->m_infosets.size(); } + }; + + class Sequences { + private: + const GameSequenceForm *m_sfg; + + public: + class iterator { + private: + const GameSequenceForm *m_sfg; + std::map>::const_iterator m_currentPlayer; + std::vector::const_iterator m_currentSequence; + + public: + iterator(const GameSequenceForm *p_sfg, bool p_end) : m_sfg(p_sfg) + { + if (p_end) { + m_currentPlayer = m_sfg->m_sequences.cend(); + } + else { + m_currentPlayer = m_sfg->m_sequences.cbegin(); + m_currentSequence = m_currentPlayer->second.cbegin(); + } + } + + GameSequence operator*() const { return *m_currentSequence; } + GameSequence operator->() const { return *m_currentSequence; } + + iterator &operator++() + { + if (m_currentPlayer == m_sfg->m_sequences.cend()) { + return *this; + } + m_currentSequence++; + if (m_currentSequence != m_currentPlayer->second.cend()) { + return *this; + } + m_currentPlayer++; + if (m_currentPlayer != m_sfg->m_sequences.cend()) { + m_currentSequence = m_currentPlayer->second.cbegin(); + } + return *this; + } + + bool operator==(const iterator &it) const + { + if (m_sfg != it.m_sfg || m_currentPlayer != it.m_currentPlayer) { + return false; + } + if (m_currentPlayer == m_sfg->m_sequences.end()) { + return true; + } + return (m_currentSequence == it.m_currentSequence); + } + bool operator!=(const iterator &it) const { return !(*this == it); } + }; + + Sequences(const GameSequenceForm *p_sfg) : m_sfg(p_sfg) {} + + size_t size() const + { + return std::accumulate( + m_sfg->m_sequences.cbegin(), m_sfg->m_sequences.cend(), 0, + [](int acc, const std::pair> &seq) { + return acc + seq.second.size(); + }); + } + + iterator begin() const { return iterator(m_sfg, false); } + iterator end() const { return iterator(m_sfg, true); } + }; + + class PlayerSequences { + private: + const GameSequenceForm *m_sfg; + GamePlayer m_player; + + public: + PlayerSequences(const GameSequenceForm *p_sfg, const GamePlayer &p_player) + : m_sfg(p_sfg), m_player(p_player) + { + } + + size_t size() const { return m_sfg->m_sequences.at(m_player).size(); } + + std::vector::const_iterator begin() const + { + return m_sfg->m_sequences.at(m_player).begin(); + } + std::vector::const_iterator end() const + { + return m_sfg->m_sequences.at(m_player).end(); + } + }; + + class Contingencies { + private: + const GameSequenceForm *m_sfg; + + public: + Contingencies(const GameSequenceForm *p_sfg) : m_sfg(p_sfg) {} + + class iterator { + private: + const GameSequenceForm *m_sfg; + bool m_end{false}; + std::map m_indices; + + public: + using iterator_category = std::input_iterator_tag; + + iterator(const GameSequenceForm *p_sfg, bool p_end = false) : m_sfg(p_sfg), m_end(p_end) + { + for (auto [player, sequences] : m_sfg->m_sequences) { + m_indices[player] = 0; + } + } + + std::map operator*() const + { + std::map ret; + for (auto [player, index] : m_indices) { + ret[player] = m_sfg->m_sequences.at(player)[index]; + } + return ret; + } + + std::map operator->() const + { + std::map ret; + for (auto [player, index] : m_indices) { + ret[player] = m_sfg->m_sequences.at(player)[index]; + } + return ret; + } + + iterator &operator++() + { + for (auto [player, index] : m_indices) { + if (index < m_sfg->m_sequences.at(player).size() - 1) { + m_indices[player]++; + return *this; + } + m_indices[player] = 0; + } + m_end = true; + return *this; + } + + bool operator==(const iterator &it) const + { + return (m_end == it.m_end && m_sfg == it.m_sfg && m_indices == it.m_indices); + } + bool operator!=(const iterator &it) const { return !(*this == it); } + }; + + iterator begin() { return iterator(m_sfg); } + iterator end() { return iterator(m_sfg, true); } + }; + + explicit GameSequenceForm(const BehaviorSupportProfile &p_support) : m_support(p_support) + { + BuildSequences(); + FillTableau(); + } + + ~GameSequenceForm() = default; + + const BehaviorSupportProfile &GetSupport() const { return m_support; } + + Sequences GetSequences() const { return Sequences(this); } + + PlayerSequences GetSequences(const GamePlayer &p_player) const + { + return PlayerSequences(this, p_player); + } + + Contingencies GetContingencies() const { return Contingencies(this); } + + Array GetPlayers() const { return m_support.GetGame()->GetPlayers(); } + + Infosets GetInfosets() const { return Infosets(this); } + + const Rational &GetPayoff(const std::map &p_profile, + const GamePlayer &p_player) const + { + return m_payoffs.at(ProfileToIndex(p_profile), p_player->GetNumber()); + } + + int GetConstraintEntry(const GameInfoset &p_infoset, const GameAction &p_action) const + { + try { + return m_constraints.at({p_infoset, p_action}); + } + catch (std::out_of_range) { + return 0; + } + } + + MixedBehaviorProfile + ToMixedBehaviorProfile(const std::map &) const; +}; + +} // end namespace Gambit + +#endif // SFG_H diff --git a/src/solvers/enumpoly/sfg.cc b/src/solvers/enumpoly/sfg.cc deleted file mode 100644 index b564c6139..000000000 --- a/src/solvers/enumpoly/sfg.cc +++ /dev/null @@ -1,153 +0,0 @@ -// -// This file is part of Gambit -// Copyright (c) 1994-2024, The Gambit Project (http://www.gambit-project.org) -// -// FILE: src/tools/enumpoly/sfg.cc -// Implementation of sequence form classes -// -// This program is free software; you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation; either version 2 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -// - -#include "sfg.h" -#include "gambit.h" - -using namespace Gambit; - -namespace Gambit { - -Sfg::Sfg(const BehaviorSupportProfile &S) : support(S) -{ - for (auto player : support.GetGame()->GetPlayers()) { - m_actionParents[player] = {}; - } - - PureSequenceProfile parent; - for (auto player : support.GetGame()->GetPlayers()) { - parent[player] = nullptr; - } - BuildSequences(support.GetGame()->GetRoot(), parent); - - Array dim(support.GetGame()->NumPlayers()); - for (int pl = 1; pl <= support.GetGame()->NumPlayers(); pl++) { - dim[pl] = m_actionParents[support.GetGame()->GetPlayer(pl)].size() + 1; - } - SF = NDArray(dim, support.GetGame()->NumPlayers()); - - for (auto player : support.GetGame()->GetPlayers()) { - m_sequenceColumns[player] = {{nullptr, 1}}; - E.try_emplace(player, infoset_row[player].size() + 1, m_actionParents[player].size() + 1); - E.at(player)(1, 1) = Rational(1); - parent[player] = nullptr; - } - - FillTableau(support.GetGame()->GetRoot(), Rational(1), parent); -} - -Rational &Sfg::GetMatrixEntry(const PureSequenceProfile &profile, const GamePlayer &player) -{ - Array index(profile.size()); - for (int pl = 1; pl <= support.GetGame()->NumPlayers(); pl++) { - index[pl] = m_sequenceColumns.at(support.GetGame()->GetPlayer(pl)) - .at(profile.at(support.GetGame()->GetPlayer(pl))); - } - return SF.at(index, player->GetNumber()); -} - -Rational &Sfg::GetConstraintEntry(const GameInfoset &infoset, const GameAction &action) -{ - return E.at(infoset->GetPlayer())(infoset_row[infoset->GetPlayer()][infoset], - m_sequenceColumns[infoset->GetPlayer()][action]); -} - -void Sfg::BuildSequences(const GameNode &n, PureSequenceProfile &p_previousActions) -{ - if (!n->GetInfoset()) { - return; - } - if (n->GetPlayer()->IsChance()) { - for (auto child : n->GetChildren()) { - BuildSequences(child, p_previousActions); - } - } - else { - auto &player_infoset_row = infoset_row[n->GetPlayer()]; - if (player_infoset_row.find(n->GetInfoset()) == player_infoset_row.end()) { - player_infoset_row[n->GetInfoset()] = player_infoset_row.size() + 1; - } - for (auto action : support.GetActions(n->GetInfoset())) { - m_actionParents[n->GetPlayer()][action] = p_previousActions[n->GetPlayer()]; - p_previousActions[n->GetPlayer()] = action; - BuildSequences(n->GetChild(action), p_previousActions); - p_previousActions[n->GetPlayer()] = m_actionParents[n->GetPlayer()][action]; - } - } -} - -void Sfg::FillTableau(const GameNode &n, const Rational &prob, - PureSequenceProfile &previousActions) -{ - if (n->GetOutcome()) { - for (auto player : support.GetGame()->GetPlayers()) { - GetMatrixEntry(previousActions, player) += - prob * static_cast(n->GetOutcome()->GetPayoff(player)); - } - } - if (!n->GetInfoset()) { - return; - } - if (n->GetPlayer()->IsChance()) { - for (auto action : n->GetInfoset()->GetActions()) { - FillTableau(n->GetChild(action), - prob * static_cast(n->GetInfoset()->GetActionProb(action)), - previousActions); - } - } - else { - GetConstraintEntry(n->GetInfoset(), previousActions.at(n->GetPlayer())) = Rational(1); - for (auto action : support.GetActions(n->GetInfoset())) { - if (m_sequenceColumns[n->GetPlayer()].find(action) == - m_sequenceColumns[n->GetPlayer()].end()) { - m_sequenceColumns[n->GetPlayer()][action] = m_sequenceColumns[n->GetPlayer()].size() + 1; - } - previousActions[n->GetPlayer()] = action; - GetConstraintEntry(n->GetInfoset(), action) = Rational(-1); - FillTableau(n->GetChild(action), prob, previousActions); - previousActions[n->GetPlayer()] = m_actionParents[n->GetPlayer()][action]; - } - } -} - -MixedBehaviorProfile Sfg::ToBehav(const PVector &x) const -{ - MixedBehaviorProfile b(support); - b = 0; - - for (int i = 1; i <= support.GetGame()->NumPlayers(); i++) { - GamePlayer player = support.GetGame()->GetPlayer(i); - for (auto action_entry : m_sequenceColumns.at(player)) { - if (action_entry.first == nullptr) { - continue; - } - auto parent = m_actionParents.at(player).at(action_entry.first); - auto parent_prob = x(player->GetNumber(), m_sequenceColumns.at(player).at(parent)); - if (parent_prob > 0) { - b[action_entry.first] = x(player->GetNumber(), action_entry.second) / parent_prob; - } - } - } - return b; -} - -} // end namespace Gambit diff --git a/src/solvers/enumpoly/sfg.h b/src/solvers/enumpoly/sfg.h deleted file mode 100644 index 69d267ce6..000000000 --- a/src/solvers/enumpoly/sfg.h +++ /dev/null @@ -1,133 +0,0 @@ -// -// This file is part of Gambit -// Copyright (c) 1994-2024, The Gambit Project (http://www.gambit-project.org) -// -// FILE: src/tools/enumpoly/sfg.h -// Interface to sequence form classes -// -// This program is free software; you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation; either version 2 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -// - -#ifndef SFG_H -#define SFG_H - -#include - -#include "gambit.h" -#include "odometer.h" -#include "ndarray.h" - -namespace Gambit { - -class Sfg { - using PureSequenceProfile = std::map; - -private: - BehaviorSupportProfile support; - std::map> m_sequenceColumns; - NDArray SF; // sequence form - std::map> E; // constraint matrices for sequence form. - std::map> infoset_row; - std::map> m_actionParents; - - void BuildSequences(const GameNode &, PureSequenceProfile &); - void FillTableau(const GameNode &, const Rational &, PureSequenceProfile &); - - Rational &GetMatrixEntry(const PureSequenceProfile &, const GamePlayer &); - Rational &GetConstraintEntry(const GameInfoset &, const GameAction &); - -public: - explicit Sfg(const BehaviorSupportProfile &); - - ~Sfg() = default; - - int NumSequences(const GamePlayer &p_player) const - { - return m_sequenceColumns.at(p_player).size(); - } - - int NumInfosets(const GamePlayer &p_player) const { return infoset_row.at(p_player).size(); } - - const Array &NumSequences() const { return SF.GetIndexDimension(); } - - int TotalNumSequences() const - { - auto &dim = SF.GetIndexDimension(); - return std::accumulate(dim.cbegin(), dim.cend(), 0); - } - - int NumPlayerInfosets() const - { - auto players = support.GetGame()->GetPlayers(); - return std::accumulate( - players.cbegin(), players.cend(), 0, - [this](int accum, const GamePlayer &player) { return accum + NumInfosets(player); }); - } - - int NumPlayers() const { return support.GetGame()->NumPlayers(); } - - const Rational &GetPayoff(const Array &index, int pl) const { return SF.at(index, pl); } - - const Rational &GetConstraintEntry(const GamePlayer &p_player, int infoset_row, - int seq_col) const - { - return E.at(p_player)(infoset_row, seq_col); - } - - int InfosetRowNumber(const GamePlayer &p_player, int sequence) const - { - if (sequence == 1) { - return 0; - } - for (auto entry : m_sequenceColumns.at(p_player)) { - if (entry.second == sequence) { - return infoset_row.at(p_player).at(entry.first->GetInfoset()); - } - } - return 0; - } - - int ActionNumber(const GamePlayer &p_player, int sequence) const - { - if (sequence == 1) { - return 0; - } - for (auto entry : m_sequenceColumns.at(p_player)) { - if (entry.second == sequence) { - return support.GetIndex(entry.first); - } - } - return 0; - } - - GameInfoset GetInfoset(const GamePlayer &p_player, int sequence) const - { - if (sequence == 1) { - return 0; - } - for (auto entry : m_sequenceColumns.at(p_player)) { - if (entry.second == sequence) { - return entry.first->GetInfoset(); - } - } - return 0; - } - - MixedBehaviorProfile ToBehav(const PVector &x) const; -}; - -} // end namespace Gambit - -#endif // SFG_H From d28a6061209dc961e3e743df422fd0f622bb1a4e Mon Sep 17 00:00:00 2001 From: Ted Turocy Date: Fri, 20 Sep 2024 13:25:03 +0100 Subject: [PATCH 15/99] Add regret-based acceptance criterion to enumpoly. --- doc/tools.enumpoly.rst | 7 +++++++ src/pygambit/gambit.pxd | 4 ++-- src/pygambit/nash.pxi | 10 ++++++---- src/pygambit/nash.py | 14 ++++++++++++-- src/solvers/enumpoly/efgpoly.cc | 8 ++++++-- src/solvers/enumpoly/enumpoly.h | 4 ++-- src/solvers/enumpoly/nfgpoly.cc | 8 ++++++-- src/tools/enumpoly/enumpoly.cc | 12 +++++++++--- src/tools/liap/liap.cc | 2 +- 9 files changed, 51 insertions(+), 18 deletions(-) diff --git a/doc/tools.enumpoly.rst b/doc/tools.enumpoly.rst index 03b79943f..dd243ece2 100644 --- a/doc/tools.enumpoly.rst +++ b/doc/tools.enumpoly.rst @@ -57,6 +57,13 @@ 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:: -q Suppresses printing of the banner at program launch. diff --git a/src/pygambit/gambit.pxd b/src/pygambit/gambit.pxd index 87a5d3238..2babfaa0e 100644 --- a/src/pygambit/gambit.pxd +++ b/src/pygambit/gambit.pxd @@ -474,10 +474,10 @@ cdef extern from "solvers/nashsupport/nashsupport.h": cdef extern from "solvers/enumpoly/enumpoly.h": c_List[c_MixedStrategyProfileDouble] EnumPolyStrategySolve( - c_Game + c_Game, float ) except +RuntimeError c_List[c_MixedBehaviorProfileDouble] EnumPolyBehaviorSolve( - c_Game + c_Game, float ) except +RuntimeError cdef extern from "solvers/logit/logit.h": diff --git a/src/pygambit/nash.pxi b/src/pygambit/nash.pxi index c644b4d7f..e7f9e0104 100644 --- a/src/pygambit/nash.pxi +++ b/src/pygambit/nash.pxi @@ -193,15 +193,17 @@ def _nashsupport_strategy_solve(game: Game) -> typing.List[StrategySupportProfil def _enumpoly_strategy_solve( - game: Game + game: Game, + maxregret: float, ) -> typing.List[MixedStrategyProfileDouble]: - return _convert_mspd(EnumPolyStrategySolve(game.game)) + return _convert_mspd(EnumPolyStrategySolve(game.game, maxregret)) def _enumpoly_behavior_solve( - game: Game + game: Game, + maxregret: float, ) -> typing.List[MixedBehaviorProfileDouble]: - return _convert_mbpd(EnumPolyBehaviorSolve(game.game)) + return _convert_mbpd(EnumPolyBehaviorSolve(game.game, maxregret)) def _logit_strategy_solve( diff --git a/src/pygambit/nash.py b/src/pygambit/nash.py index f6fe32d7b..788a58282 100644 --- a/src/pygambit/nash.py +++ b/src/pygambit/nash.py @@ -550,6 +550,7 @@ def possible_nash_supports(game: libgbt.Game) -> list[libgbt.StrategySupportProf def enumpoly_solve( game: libgbt.Game, + maxregret: float = 1.0e-4, use_strategic: bool = False, ) -> NashComputationResult: """Compute Nash equilibria by solving systems of polynomial equations. @@ -558,6 +559,12 @@ def enumpoly_solve( ---------- game : Game The game to compute equilibria in. + + 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 + 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. @@ -567,15 +574,18 @@ def enumpoly_solve( res : NashComputationResult The result represented as a ``NashComputationResult`` object. """ + if maxregret <= 0.0: + raise ValueError(f"maxregret must be a positive number; got {maxregret}") if not game.is_tree or use_strategic: - equilibria = libgbt._enumpoly_strategy_solve(game) + equilibria = libgbt._enumpoly_strategy_solve(game, maxregret) else: - equilibria = libgbt._enumpoly_behavior_solve(game) + equilibria = libgbt._enumpoly_behavior_solve(game, maxregret) return NashComputationResult( game=game, method="enumpoly", rational=False, use_strategic=not game.is_tree or use_strategic, + parameters={"maxregret": maxregret}, equilibria=equilibria, ) diff --git a/src/solvers/enumpoly/efgpoly.cc b/src/solvers/enumpoly/efgpoly.cc index 82a10e0c9..9249af7aa 100644 --- a/src/solvers/enumpoly/efgpoly.cc +++ b/src/solvers/enumpoly/efgpoly.cc @@ -204,10 +204,14 @@ namespace Gambit { namespace Nash { List> -EnumPolyBehaviorSolve(const Game &p_game, +EnumPolyBehaviorSolve(const Game &p_game, double p_maxregret, EnumPolyMixedBehaviorObserverFunctionType p_onEquilibrium, EnumPolyBehaviorSupportObserverFunctionType p_onSupport) { + if (double scale = p_game->GetMaxPayoff() - p_game->GetMinPayoff() != 0.0) { + p_maxregret *= scale; + } + List> ret; auto possible_supports = PossibleNashBehaviorSupports(p_game); @@ -216,7 +220,7 @@ EnumPolyBehaviorSolve(const Game &p_game, bool isSingular = false; for (auto solution : SolveSupport(support, isSingular)) { MixedBehaviorProfile fullProfile = solution.ToFullSupport(); - if (fullProfile.GetLiapValue() < 1.0e-6) { + if (fullProfile.GetMaxRegret() < p_maxregret) { p_onEquilibrium(fullProfile); ret.push_back(fullProfile); } diff --git a/src/solvers/enumpoly/enumpoly.h b/src/solvers/enumpoly/enumpoly.h index 25d16d601..9755c6b05 100644 --- a/src/solvers/enumpoly/enumpoly.h +++ b/src/solvers/enumpoly/enumpoly.h @@ -48,7 +48,7 @@ EnumPolyStrategySupportSolve(const StrategySupportProfile &support, bool &is_sin int p_stopAfter = 0); List> EnumPolyStrategySolve( - const Game &, + const Game &, double p_maxregret, EnumPolyMixedStrategyObserverFunctionType p_onEquilibrium = EnumPolyNullMixedStrategyObserver, EnumPolyStrategySupportObserverFunctionType p_onSupport = EnumPolyNullStrategySupportObserver); @@ -66,7 +66,7 @@ inline void EnumPolyNullBehaviorSupportObserver(const std::string &, } List> EnumPolyBehaviorSolve( - const Game &, + const Game &, double p_maxregret, EnumPolyMixedBehaviorObserverFunctionType p_onEquilibrium = EnumPolyNullMixedBehaviorObserver, EnumPolyBehaviorSupportObserverFunctionType p_onSupport = EnumPolyNullBehaviorSupportObserver); diff --git a/src/solvers/enumpoly/nfgpoly.cc b/src/solvers/enumpoly/nfgpoly.cc index 1b89a1a5d..d9d8f3456 100644 --- a/src/solvers/enumpoly/nfgpoly.cc +++ b/src/solvers/enumpoly/nfgpoly.cc @@ -143,10 +143,14 @@ EnumPolyStrategySupportSolve(const StrategySupportProfile &support, bool &is_sin } List> -EnumPolyStrategySolve(const Game &p_game, +EnumPolyStrategySolve(const Game &p_game, double p_maxregret, EnumPolyMixedStrategyObserverFunctionType p_onEquilibrium, EnumPolyStrategySupportObserverFunctionType p_onSupport) { + if (double scale = p_game->GetMaxPayoff() - p_game->GetMinPayoff() != 0.0) { + p_maxregret *= scale; + } + List> ret; auto possible_supports = PossibleNashStrategySupports(p_game); @@ -155,7 +159,7 @@ EnumPolyStrategySolve(const Game &p_game, bool is_singular; for (auto solution : EnumPolyStrategySupportSolve(support, is_singular)) { MixedStrategyProfile fullProfile = solution.ToFullSupport(); - if (fullProfile.GetLiapValue() < 1.0e-6) { + if (fullProfile.GetMaxRegret() < p_maxregret) { p_onEquilibrium(fullProfile); ret.push_back(fullProfile); } diff --git a/src/tools/enumpoly/enumpoly.cc b/src/tools/enumpoly/enumpoly.cc index 1395941b3..026857818 100644 --- a/src/tools/enumpoly/enumpoly.cc +++ b/src/tools/enumpoly/enumpoly.cc @@ -55,6 +55,8 @@ void PrintHelp(char *progname) std::cerr << " -S use strategic game\n"; std::cerr << " -H use heuristic search method to optimize time\n"; std::cerr << " to find first equilibrium (strategic games only)\n"; + std::cerr << " -m MAXREGRET maximum regret acceptable as a proportion of range of\n"; + std::cerr << " payoffs in the game\n"; std::cerr << " -q quiet mode (suppresses banner)\n"; std::cerr << " -V, --verbose verbose mode (shows supports investigated)\n"; std::cerr << " -v, --version print version information\n"; @@ -124,6 +126,7 @@ int main(int argc, char *argv[]) bool quiet = false; bool useHeuristic = false, useStrategic = false; + double maxregret = 1.0e-4; int long_opt_index = 0; struct option long_options[] = {{"help", 0, nullptr, 'h'}, @@ -131,7 +134,7 @@ int main(int argc, char *argv[]) {"verbose", 0, nullptr, 'V'}, {nullptr, 0, nullptr, 0}}; int c; - while ((c = getopt_long(argc, argv, "d:hHSqvV", long_options, &long_opt_index)) != -1) { + while ((c = getopt_long(argc, argv, "d:hHSm:qvV", long_options, &long_opt_index)) != -1) { switch (c) { case 'v': PrintBanner(std::cerr); @@ -148,6 +151,9 @@ int main(int argc, char *argv[]) case 'S': useStrategic = true; break; + case 'm': + maxregret = atof(optarg); + break; case 'q': quiet = true; break; @@ -198,7 +204,7 @@ int main(int argc, char *argv[]) } else { EnumPolyStrategySolve( - game, + game, maxregret, [](const MixedStrategyProfile &eqm) { PrintProfile(std::cout, "NE", eqm); }, [](const std::string &label, const StrategySupportProfile &support) { PrintSupport(std::cout, label, support); @@ -207,7 +213,7 @@ int main(int argc, char *argv[]) } else { EnumPolyBehaviorSolve( - game, + game, maxregret, [](const MixedBehaviorProfile &eqm) { PrintProfile(std::cout, "NE", eqm); }, [](const std::string &label, const BehaviorSupportProfile &support) { PrintSupport(std::cout, label, support); diff --git a/src/tools/liap/liap.cc b/src/tools/liap/liap.cc index 869141f79..ba41d0f23 100644 --- a/src/tools/liap/liap.cc +++ b/src/tools/liap/liap.cc @@ -12,7 +12,7 @@ // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// MERCHANTABILITY or FITNESS FO fR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License From c64a3bac977586022172851abd19d4c4bc3b693d Mon Sep 17 00:00:00 2001 From: Ted Turocy Date: Fri, 20 Sep 2024 13:37:58 +0100 Subject: [PATCH 16/99] Debridement and modernisation of behavextend This does some cleanup and updating of behavextend. This involves no functionality changes, but removes unneeded/duplicate code, and uses modern features like STL containers and range-based for-loops where it is straightforward to do so. This is not a complete modernisation as there are a few places where modernisation is not just a trivial reworking. In addition, it is not at all clear that these functions are necessarily fully correct, or that the task they are solving could not be handled more easily! --- src/solvers/enumpoly/behavextend.cc | 467 +++++++++++----------------- src/solvers/enumpoly/behavextend.h | 35 ++- src/solvers/enumpoly/efgpoly.cc | 11 +- 3 files changed, 212 insertions(+), 301 deletions(-) diff --git a/src/solvers/enumpoly/behavextend.cc b/src/solvers/enumpoly/behavextend.cc index 25872d453..9d7aaf2b4 100644 --- a/src/solvers/enumpoly/behavextend.cc +++ b/src/solvers/enumpoly/behavextend.cc @@ -26,101 +26,77 @@ #include "rectangl.h" #include "ineqsolv.h" -void TerminalDescendants(const Gambit::GameNode &p_node, Gambit::List ¤t) +using namespace Gambit; + +namespace { + +void TerminalDescendants(const GameNode &p_node, std::list ¤t) { if (p_node->IsTerminal()) { current.push_back(p_node); } else { - for (int i = 1; i <= p_node->NumChildren(); i++) { - TerminalDescendants(p_node->GetChild(i), current); + for (auto child : p_node->GetChildren()) { + TerminalDescendants(child, current); } } } -Gambit::List TerminalNodes(const Gambit::Game &p_efg) +std::list TerminalNodes(const Game &p_efg) { - Gambit::List ret; + std::list ret; TerminalDescendants(p_efg->GetRoot(), ret); return ret; } -// -// Design choice: the auxiliary functions here are made static -// rather than members to help hide the gPoly-related details of -// the implementation. Some of these functions might be more -// generally useful, in which case they should be made visible -// somehow. Also, a namespace would be preferable to using -// static, but static is used for portability. -- TLT, 5/2001. -// - -//========================================================================= -// class algExtendsToNash -//========================================================================= - -static void DeviationInfosets(Gambit::List &answer, - const Gambit::BehaviorSupportProfile &big_supp, - const Gambit::GamePlayer &pl, const Gambit::GameNode &node, - const Gambit::GameAction &act) +void DeviationInfosets(List &answer, const BehaviorSupportProfile &big_supp, + const GamePlayer &pl, const GameNode &node, const GameAction &act) { - Gambit::GameNode child = node->GetChild(act->GetNumber()); - if (!child->IsTerminal()) { - Gambit::GameInfoset iset = child->GetInfoset(); - if (iset->GetPlayer() == pl) { - int insert = 0; - bool done = false; - while (!done) { - insert++; - if (insert > answer.Length() || iset->Precedes(answer[insert]->GetMember(1))) { - done = true; - } + GameNode child = node->GetChild(act); + if (child->IsTerminal()) { + return; + } + GameInfoset iset = child->GetInfoset(); + if (iset->GetPlayer() == pl) { + int insert = 0; + bool done = false; + while (!done) { + insert++; + if (insert > answer.size() || iset->Precedes(answer[insert]->GetMember(1))) { + done = true; } - answer.Insert(iset, insert); } + answer.Insert(iset, insert); + } - Gambit::List action_list; - for (int j = 1; j <= iset->NumActions(); j++) { - action_list.push_back(iset->GetAction(j)); - } - for (int j = 1; j <= action_list.Length(); j++) { - DeviationInfosets(answer, big_supp, pl, child, action_list[j]); - } + for (auto action : iset->GetActions()) { + DeviationInfosets(answer, big_supp, pl, child, action); } } -static Gambit::List -DeviationInfosets(const Gambit::BehaviorSupportProfile &big_supp, const Gambit::GamePlayer &pl, - const Gambit::GameInfoset &iset, const Gambit::GameAction &act) +List DeviationInfosets(const BehaviorSupportProfile &big_supp, const GamePlayer &pl, + const GameInfoset &iset, const GameAction &act) { - Gambit::List answer; - - Gambit::List node_list; - for (int i = 1; i <= iset->NumMembers(); i++) { - node_list.push_back(iset->GetMember(i)); - } - - for (int i = 1; i <= node_list.Length(); i++) { - DeviationInfosets(answer, big_supp, pl, node_list[i], act); + List answer; + for (auto member : iset->GetMembers()) { + DeviationInfosets(answer, big_supp, pl, member, act); } - return answer; } -static gPolyList -ActionProbsSumToOneIneqs(const Gambit::MixedBehaviorProfile &p_solution, - const gSpace &BehavStratSpace, const term_order &Lex, - const Gambit::BehaviorSupportProfile &big_supp, - const Gambit::List> &var_index) +gPolyList ActionProbsSumToOneIneqs(const MixedBehaviorProfile &p_solution, + const gSpace &BehavStratSpace, const term_order &Lex, + const BehaviorSupportProfile &big_supp, + const std::map &var_index) { gPolyList answer(&BehavStratSpace, &Lex); - for (int pl = 1; pl <= p_solution.GetGame()->NumPlayers(); pl++) { - for (int i = 1; i <= p_solution.GetGame()->GetPlayer(pl)->NumInfosets(); i++) { - Gambit::GameInfoset current_infoset = p_solution.GetGame()->GetPlayer(pl)->GetInfoset(i); - if (!big_supp.HasAction(current_infoset)) { - int index_base = var_index[pl][i]; - gPoly factor(&BehavStratSpace, (double)1.0, &Lex); - for (int k = 1; k < current_infoset->NumActions(); k++) { + for (auto player : p_solution.GetGame()->GetPlayers()) { + for (auto infoset : player->GetInfosets()) { + if (!big_supp.HasAction(infoset)) { + int index_base = var_index.at(infoset); + gPoly factor(&BehavStratSpace, 1.0, &Lex); + for (int k = 1; k < infoset->NumActions(); k++) { factor -= gPoly(&BehavStratSpace, index_base + k, 1, &Lex); } answer += factor; @@ -130,21 +106,17 @@ ActionProbsSumToOneIneqs(const Gambit::MixedBehaviorProfile &p_solution, return answer; } -static Gambit::List -DeviationSupports(const Gambit::BehaviorSupportProfile &big_supp, - const Gambit::List &isetlist, - const Gambit::GamePlayer & /*pl*/, const Gambit::GameInfoset & /*iset*/, - const Gambit::GameAction & /*act*/) +std::list DeviationSupports(const BehaviorSupportProfile &big_supp, + const List &isetlist) { - Gambit::List answer; - - Gambit::Array active_act_no(isetlist.Length()); + std::list answer; + Array active_act_no(isetlist.size()); for (int k = 1; k <= active_act_no.Length(); k++) { active_act_no[k] = 0; } - Gambit::BehaviorSupportProfile new_supp(big_supp); + BehaviorSupportProfile new_supp(big_supp); for (int i = 1; i <= isetlist.Length(); i++) { for (int j = 1; j < isetlist[i]->NumActions(); j++) { @@ -204,18 +176,15 @@ DeviationSupports(const Gambit::BehaviorSupportProfile &big_supp, return answer; } -static bool NashNodeProbabilityPoly(const Gambit::MixedBehaviorProfile &p_solution, - gPoly &node_prob, const gSpace &BehavStratSpace, - const term_order &Lex, - const Gambit::BehaviorSupportProfile &dsupp, - const Gambit::List> &var_index, - Gambit::GameNode tempnode, const Gambit::GamePlayer & /*pl*/, - const Gambit::GameInfoset &iset, const Gambit::GameAction &act) +bool NashNodeProbabilityPoly(const MixedBehaviorProfile &p_solution, + gPoly &node_prob, const gSpace &BehavStratSpace, + const term_order &Lex, const BehaviorSupportProfile &dsupp, + const std::map &var_index, GameNode tempnode, + const GameInfoset &iset, const GameAction &act) { while (tempnode != p_solution.GetGame()->GetRoot()) { - - Gambit::GameAction last_action = tempnode->GetPriorAction(); - Gambit::GameInfoset last_infoset = last_action->GetInfoset(); + GameAction last_action = tempnode->GetPriorAction(); + GameInfoset last_infoset = last_action->GetInfoset(); if (last_infoset->IsChanceInfoset()) { node_prob *= static_cast(last_infoset->GetActionProb(last_action->GetNumber())); @@ -229,7 +198,7 @@ static bool NashNodeProbabilityPoly(const Gambit::MixedBehaviorProfile & else if (dsupp.Contains(last_action)) { if (last_action->GetInfoset()->GetPlayer() != act->GetInfoset()->GetPlayer() || !act->Precedes(tempnode)) { - node_prob *= (double)p_solution.GetActionProb(last_action); + node_prob *= p_solution.GetActionProb(last_action); } } else { @@ -237,16 +206,14 @@ static bool NashNodeProbabilityPoly(const Gambit::MixedBehaviorProfile & } } else { - int initial_var_no = - var_index[last_infoset->GetPlayer()->GetNumber()][last_infoset->GetNumber()]; + int initial_var_no = var_index.at(last_infoset); if (last_action->GetNumber() < last_infoset->NumActions()) { int varno = initial_var_no + last_action->GetNumber(); node_prob *= gPoly(&BehavStratSpace, varno, 1, &Lex); } else { - gPoly factor(&BehavStratSpace, (double)1.0, &Lex); - int k; - for (k = 1; k < last_infoset->NumActions(); k++) { + gPoly factor(&BehavStratSpace, 1.0, &Lex); + for (int k = 1; k < last_infoset->NumActions(); k++) { factor -= gPoly(&BehavStratSpace, initial_var_no + k, 1, &Lex); } node_prob *= factor; @@ -257,61 +224,44 @@ static bool NashNodeProbabilityPoly(const Gambit::MixedBehaviorProfile & return true; } -static gPolyList -NashExpectedPayoffDiffPolys(const Gambit::MixedBehaviorProfile &p_solution, - const gSpace &BehavStratSpace, const term_order &Lex, - const Gambit::BehaviorSupportProfile &little_supp, - const Gambit::BehaviorSupportProfile &big_supp, - const Gambit::List> &var_index) +gPolyList NashExpectedPayoffDiffPolys(const MixedBehaviorProfile &p_solution, + const gSpace &BehavStratSpace, const term_order &Lex, + const BehaviorSupportProfile &little_supp, + const BehaviorSupportProfile &big_supp, + const std::map &var_index) { gPolyList answer(&BehavStratSpace, &Lex); - Gambit::List terminal_nodes = TerminalNodes(p_solution.GetGame()); - - for (int pl = 1; pl <= p_solution.GetGame()->NumPlayers(); pl++) { - Gambit::Array isets_for_pl; - for (int iset = 1; iset <= p_solution.GetGame()->GetPlayer(pl)->NumInfosets(); iset++) { - isets_for_pl.push_back(p_solution.GetGame()->GetPlayer(pl)->GetInfoset(iset)); - } + auto terminal_nodes = TerminalNodes(p_solution.GetGame()); - for (int i = 1; i <= isets_for_pl.Length(); i++) { - if (little_supp.IsReachable(isets_for_pl[i])) { - Gambit::Array acts_for_iset; - for (int act = 1; act <= isets_for_pl[i]->NumActions(); act++) { - acts_for_iset.push_back(isets_for_pl[i]->GetAction(act)); + for (auto player : p_solution.GetGame()->GetPlayers()) { + for (auto infoset : player->GetInfosets()) { + if (!little_supp.IsReachable(infoset)) { + continue; + } + for (auto action : infoset->GetActions()) { + if (little_supp.Contains(action)) { + continue; } - - for (int j = 1; j <= acts_for_iset.Length(); j++) { - if (!little_supp.Contains(acts_for_iset[j])) { - Gambit::List isetlist = DeviationInfosets( - big_supp, p_solution.GetGame()->GetPlayer(pl), isets_for_pl[i], acts_for_iset[j]); - Gambit::List dsupps = - DeviationSupports(big_supp, isetlist, p_solution.GetGame()->GetPlayer(pl), - isets_for_pl[i], acts_for_iset[j]); - for (int k = 1; k <= dsupps.Length(); k++) { - - // This will be the utility difference between the - // payoff resulting from the profile and deviation to - // the strategy for pl specified by dsupp[k] - - gPoly next_poly(&BehavStratSpace, &Lex); - - for (int n = 1; n <= terminal_nodes.Length(); n++) { - gPoly node_prob(&BehavStratSpace, (double)1.0, &Lex); - if (NashNodeProbabilityPoly(p_solution, node_prob, BehavStratSpace, Lex, dsupps[k], - var_index, terminal_nodes[n], - p_solution.GetGame()->GetPlayer(pl), isets_for_pl[i], - acts_for_iset[j])) { - if (terminal_nodes[n]->GetOutcome()) { - node_prob *= - static_cast(terminal_nodes[n]->GetOutcome()->GetPayoff(pl)); - } - next_poly += node_prob; - } + auto isetlist = DeviationInfosets(big_supp, player, infoset, action); + auto dsupps = DeviationSupports(big_supp, isetlist); + for (auto support : dsupps) { + // The utility difference between the + // payoff resulting from the profile and deviation to + // the strategy for pl specified by dsupp[k] + gPoly next_poly(&BehavStratSpace, &Lex); + + for (auto node : terminal_nodes) { + gPoly node_prob(&BehavStratSpace, 1.0, &Lex); + if (NashNodeProbabilityPoly(p_solution, node_prob, BehavStratSpace, Lex, support, + var_index, node, infoset, action)) { + if (node->GetOutcome()) { + node_prob *= static_cast(node->GetOutcome()->GetPayoff(player)); } - answer += -next_poly + (double)p_solution.GetPayoff(pl); + next_poly += node_prob; } } + answer += -next_poly + p_solution.GetPayoff(player); } } } @@ -319,47 +269,40 @@ NashExpectedPayoffDiffPolys(const Gambit::MixedBehaviorProfile &p_soluti return answer; } -static gPolyList ExtendsToNashIneqs(const Gambit::MixedBehaviorProfile &p_solution, - const gSpace &BehavStratSpace, const term_order &Lex, - const Gambit::BehaviorSupportProfile &little_supp, - const Gambit::BehaviorSupportProfile &big_supp, - const Gambit::List> &var_index) +gPolyList ExtendsToNashIneqs(const MixedBehaviorProfile &p_solution, + const gSpace &BehavStratSpace, const term_order &Lex, + const BehaviorSupportProfile &little_supp, + const BehaviorSupportProfile &big_supp, + const std::map &var_index) { gPolyList answer(&BehavStratSpace, &Lex); answer += ActionProbsSumToOneIneqs(p_solution, BehavStratSpace, Lex, big_supp, var_index); - answer += NashExpectedPayoffDiffPolys(p_solution, BehavStratSpace, Lex, little_supp, big_supp, var_index); return answer; } -bool algExtendsToNash::ExtendsToNash(const Gambit::MixedBehaviorProfile &p_solution, - const Gambit::BehaviorSupportProfile &little_supp, - const Gambit::BehaviorSupportProfile &big_supp) -{ - // This asks whether there is a Nash extension of the Gambit::MixedBehaviorProfile to - // all information sets at which the behavioral probabilities are not - // specified. The assumption is that the support has active actions - // at infosets at which the behavioral probabilities are defined, and - // no others. Also, the BehavSol is assumed to be already a Nash - // equilibrium for the truncated game obtained by eliminating stuff - // outside little_supp. +} // end anonymous namespace - // First we compute the number of variables, and indexing information - int num_vars(0); - Gambit::List> var_index; - int pl; - for (pl = 1; pl <= p_solution.GetGame()->NumPlayers(); pl++) { +namespace Gambit { +namespace Nash { - Gambit::List list_for_pl; +bool ExtendsToNash(const MixedBehaviorProfile &p_solution, + const BehaviorSupportProfile &little_supp, + const BehaviorSupportProfile &big_supp) +{ - for (int i = 1; i <= p_solution.GetGame()->GetPlayer(pl)->NumInfosets(); i++) { - list_for_pl.push_back(num_vars); - if (!big_supp.HasAction(p_solution.GetGame()->GetPlayer(pl)->GetInfoset(i))) { - num_vars += p_solution.GetGame()->GetPlayer(pl)->GetInfoset(i)->NumActions() - 1; + // First we compute the number of variables, and indexing information + int num_vars = 0; + std::map var_index; + for (auto player : p_solution.GetGame()->GetPlayers()) { + List list_for_pl; + for (auto infoset : player->GetInfosets()) { + var_index[infoset] = num_vars; + if (!big_supp.HasAction(infoset)) { + num_vars += infoset->NumActions() - 1; } } - var_index.push_back(list_for_pl); } // We establish the space @@ -367,41 +310,32 @@ bool algExtendsToNash::ExtendsToNash(const Gambit::MixedBehaviorProfile ORD_PTR ptr = &lex; term_order Lex(&BehavStratSpace, ptr); - num_vars = BehavStratSpace.Dmnsn(); - gPolyList inequalities = ExtendsToNashIneqs(p_solution, BehavStratSpace, Lex, little_supp, big_supp, var_index); // set up the rectangle of search - Gambit::Vector bottoms(num_vars), tops(num_vars); - bottoms = (double)0; - tops = (double)1; - gRectangle Cube(bottoms, tops); + Vector bottoms(num_vars), tops(num_vars); + bottoms = 0; + tops = 1; // Set up the test and do it - IneqSolv extension_tester(inequalities); - Gambit::Vector sample(num_vars); - bool answer = extension_tester.ASolutionExists(Cube, sample); + Vector sample(num_vars); + return IneqSolv(inequalities).ASolutionExists(gRectangle(bottoms, tops), sample); +} - // assert (answer == m_profile->ExtendsToNash(little_supp, big_supp, m_status)); +} // namespace Nash +} // end namespace Gambit - return answer; -} +namespace { -//========================================================================= -// class algExtendsToAgentNash -//========================================================================= - -static bool ANFNodeProbabilityPoly(const Gambit::MixedBehaviorProfile &p_solution, - gPoly &node_prob, const gSpace &BehavStratSpace, - const term_order &Lex, - const Gambit::BehaviorSupportProfile &big_supp, - const Gambit::List> &var_index, - Gambit::GameNode tempnode, const int &pl, const int &i, - const int &j) +bool ANFNodeProbabilityPoly(const MixedBehaviorProfile &p_solution, + gPoly &node_prob, const gSpace &BehavStratSpace, + const term_order &Lex, const BehaviorSupportProfile &big_supp, + const std::map &var_index, GameNode tempnode, int pl, + int i, int j) { while (tempnode != p_solution.GetGame()->GetRoot()) { - Gambit::GameAction last_action = tempnode->GetPriorAction(); - Gambit::GameInfoset last_infoset = last_action->GetInfoset(); + GameAction last_action = tempnode->GetPriorAction(); + GameInfoset last_infoset = last_action->GetInfoset(); if (last_infoset->IsChanceInfoset()) { node_prob *= static_cast(last_infoset->GetActionProb(last_action->GetNumber())); @@ -413,23 +347,21 @@ static bool ANFNodeProbabilityPoly(const Gambit::MixedBehaviorProfile &p } } else if (big_supp.Contains(last_action)) { - node_prob *= (double)p_solution.GetActionProb(last_action); + node_prob *= p_solution.GetActionProb(last_action); } else { return false; } } else { - int initial_var_no = - var_index[last_infoset->GetPlayer()->GetNumber()][last_infoset->GetNumber()]; + int initial_var_no = var_index.at(last_infoset); if (last_action->GetNumber() < last_infoset->NumActions()) { int varno = initial_var_no + last_action->GetNumber(); node_prob *= gPoly(&BehavStratSpace, varno, 1, &Lex); } else { - gPoly factor(&BehavStratSpace, (double)1.0, &Lex); - int k; - for (k = 1; k < last_infoset->NumActions(); k++) { + gPoly factor(&BehavStratSpace, 1.0, &Lex); + for (int k = 1; k < last_infoset->NumActions(); k++) { factor -= gPoly(&BehavStratSpace, initial_var_no + k, 1, &Lex); } node_prob *= factor; @@ -440,54 +372,52 @@ static bool ANFNodeProbabilityPoly(const Gambit::MixedBehaviorProfile &p return true; } -static gPolyList -ANFExpectedPayoffDiffPolys(const Gambit::MixedBehaviorProfile &p_solution, - const gSpace &BehavStratSpace, const term_order &Lex, - const Gambit::BehaviorSupportProfile &little_supp, - const Gambit::BehaviorSupportProfile &big_supp, - const Gambit::List> &var_index) +gPolyList ANFExpectedPayoffDiffPolys(const MixedBehaviorProfile &p_solution, + const gSpace &BehavStratSpace, const term_order &Lex, + const BehaviorSupportProfile &little_supp, + const BehaviorSupportProfile &big_supp, + const std::map &var_index) { gPolyList answer(&BehavStratSpace, &Lex); - Gambit::List terminal_nodes = TerminalNodes(p_solution.GetGame()); - - for (int pl = 1; pl <= p_solution.GetGame()->NumPlayers(); pl++) { - for (int i = 1; i <= p_solution.GetGame()->GetPlayer(pl)->NumInfosets(); i++) { - Gambit::GameInfoset infoset = p_solution.GetGame()->GetPlayer(pl)->GetInfoset(i); - if (little_supp.IsReachable(infoset)) { - for (int j = 1; j <= infoset->NumActions(); j++) { - if (!little_supp.Contains(infoset->GetAction(j))) { - - // This will be the utility difference between the - // payoff resulting from the profile and deviation to - // action j - gPoly next_poly(&BehavStratSpace, &Lex); - - for (int n = 1; n <= terminal_nodes.Length(); n++) { - gPoly node_prob(&BehavStratSpace, (double)1.0, &Lex); - if (ANFNodeProbabilityPoly(p_solution, node_prob, BehavStratSpace, Lex, big_supp, - var_index, terminal_nodes[n], pl, i, j)) { - if (terminal_nodes[n]->GetOutcome()) { - node_prob *= static_cast(terminal_nodes[n]->GetOutcome()->GetPayoff(pl)); - } - next_poly += node_prob; - } + auto terminal_nodes = TerminalNodes(p_solution.GetGame()); + + for (auto player : p_solution.GetGame()->GetPlayers()) { + for (auto infoset : player->GetInfosets()) { + if (!little_supp.IsReachable(infoset)) { + continue; + } + for (auto action : infoset->GetActions()) { + if (little_supp.Contains(action)) { + continue; + } + // This will be the utility difference between the + // payoff resulting from the profile and deviation to + // action j + gPoly next_poly(&BehavStratSpace, &Lex); + for (auto terminal : terminal_nodes) { + gPoly node_prob(&BehavStratSpace, 1.0, &Lex); + if (ANFNodeProbabilityPoly(p_solution, node_prob, BehavStratSpace, Lex, big_supp, + var_index, terminal, player->GetNumber(), + infoset->GetNumber(), action->GetNumber())) { + if (terminal->GetOutcome()) { + node_prob *= static_cast(terminal->GetOutcome()->GetPayoff(player)); } - answer += -next_poly + (double)p_solution.GetPayoff(pl); + next_poly += node_prob; } } + answer += -next_poly + p_solution.GetPayoff(player); } } } return answer; } -static gPolyList -ExtendsToANFNashIneqs(const Gambit::MixedBehaviorProfile &p_solution, - const gSpace &BehavStratSpace, const term_order &Lex, - const Gambit::BehaviorSupportProfile &little_supp, - const Gambit::BehaviorSupportProfile &big_supp, - const Gambit::List> &var_index) +gPolyList ExtendsToANFNashIneqs(const MixedBehaviorProfile &p_solution, + const gSpace &BehavStratSpace, const term_order &Lex, + const BehaviorSupportProfile &little_supp, + const BehaviorSupportProfile &big_supp, + const std::map &var_index) { gPolyList answer(&BehavStratSpace, &Lex); answer += ActionProbsSumToOneIneqs(p_solution, BehavStratSpace, Lex, big_supp, var_index); @@ -496,31 +426,25 @@ ExtendsToANFNashIneqs(const Gambit::MixedBehaviorProfile &p_solution, return answer; } -bool algExtendsToAgentNash::ExtendsToAgentNash( - const Gambit::MixedBehaviorProfile &p_solution, - const Gambit::BehaviorSupportProfile &little_supp, - const Gambit::BehaviorSupportProfile &big_supp) -{ - // This asks whether there is an ANF Nash extension of the Gambit::MixedBehaviorProfile - // to all information sets at which the behavioral probabilities are not specified. The - // assumption is that the support has active actions at infosets at which the behavioral - // probabilities are defined, and no others. - - // First we compute the number of variables, and indexing information - int num_vars(0); - Gambit::List> var_index; - int pl; - for (pl = 1; pl <= p_solution.GetGame()->NumPlayers(); pl++) { +} // end anonymous namespace - Gambit::List list_for_pl; +namespace Gambit { +namespace Nash { - for (int i = 1; i <= p_solution.GetGame()->GetPlayer(pl)->NumInfosets(); i++) { - list_for_pl.push_back(num_vars); - if (!big_supp.HasAction(p_solution.GetGame()->GetPlayer(pl)->GetInfoset(i))) { - num_vars += p_solution.GetGame()->GetPlayer(pl)->GetInfoset(i)->NumActions() - 1; +bool ExtendsToAgentNash(const MixedBehaviorProfile &p_solution, + const BehaviorSupportProfile &little_supp, + const BehaviorSupportProfile &big_supp) +{ + // First we compute the number of variables, and indexing information + int num_vars = 0; + std::map var_index; + for (auto player : p_solution.GetGame()->GetPlayers()) { + for (auto infoset : player->GetInfosets()) { + var_index[infoset] = num_vars; + if (!big_supp.HasAction(infoset)) { + num_vars += infoset->NumActions() - 1; } } - var_index.push_back(list_for_pl); } // We establish the space @@ -533,33 +457,14 @@ bool algExtendsToAgentNash::ExtendsToAgentNash( ExtendsToANFNashIneqs(p_solution, BehavStratSpace, Lex, little_supp, big_supp, var_index); // set up the rectangle of search - Gambit::Vector bottoms(num_vars), tops(num_vars); - bottoms = (double)0; - tops = (double)1; - gRectangle Cube(bottoms, tops); + Vector bottoms(num_vars), tops(num_vars); + bottoms = 0; + tops = 1; // Set up the test and do it - IneqSolv extension_tester(inequalities); - Gambit::Vector sample(num_vars); - - // Temporarily, we check the old set up vs. the new - bool ANFanswer = extension_tester.ASolutionExists(Cube, sample); - // assert (ANFanswer == m_profile->ExtendsToANFNash(little_supp, - // big_supp, - // m_status)); - - /* - bool NASHanswer = m_profile->ExtendsToNash(Support(),Support(),m_status); - - //DEBUG - if (ANFanswer && !NASHanswer) - gout << - "The following should be extendable to an ANF Nash, but not to a Nash:\n" - << *m_profile << "\n\n"; - if (NASHanswer && !ANFanswer) - gout << - "ERROR: said to be extendable to a Nash, but not to an ANF Nash:\n" - << *m_profile << "\n\n"; - */ - return ANFanswer; + Vector sample(num_vars); + return IneqSolv(inequalities).ASolutionExists(gRectangle(bottoms, tops), sample); } + +} // namespace Nash +} // end namespace Gambit diff --git a/src/solvers/enumpoly/behavextend.h b/src/solvers/enumpoly/behavextend.h index 837fe2919..be8f5276e 100644 --- a/src/solvers/enumpoly/behavextend.h +++ b/src/solvers/enumpoly/behavextend.h @@ -25,18 +25,29 @@ #include "gambit.h" -class algExtendsToNash { -public: - bool ExtendsToNash(const Gambit::MixedBehaviorProfile &p_solution, - const Gambit::BehaviorSupportProfile &p_littleSupport, - const Gambit::BehaviorSupportProfile &p_bigSupport); -}; +namespace Gambit { +namespace Nash { -class algExtendsToAgentNash { -public: - bool ExtendsToAgentNash(const Gambit::MixedBehaviorProfile &p_solution, - const Gambit::BehaviorSupportProfile &p_littleSupport, - const Gambit::BehaviorSupportProfile &p_bigSupport); -}; +// This asks whether there is a Nash extension of the MixedBehaviorProfile to +// all information sets at which the behavioral probabilities are not +// specified. The assumption is that the support has active actions +// at infosets at which the behavioral probabilities are defined, and +// no others. Also, the BehavSol is assumed to be already a Nash +// equilibrium for the truncated game obtained by eliminating stuff +// outside little_supp. +bool ExtendsToNash(const MixedBehaviorProfile &p_solution, + const BehaviorSupportProfile &p_littleSupport, + const BehaviorSupportProfile &p_bigSupport); + +// This asks whether there is an ANF Nash extension of the MixedBehaviorProfile +// to all information sets at which the behavioral probabilities are not specified. The +// assumption is that the support has active actions at infosets at which the behavioral +// probabilities are defined, and no others. +bool ExtendsToAgentNash(const MixedBehaviorProfile &p_solution, + const BehaviorSupportProfile &p_littleSupport, + const BehaviorSupportProfile &p_bigSupport); + +} // namespace Nash +} // namespace Gambit #endif // BEHAVEXTEND_H diff --git a/src/solvers/enumpoly/efgpoly.cc b/src/solvers/enumpoly/efgpoly.cc index 9249af7aa..be9dc1ab6 100644 --- a/src/solvers/enumpoly/efgpoly.cc +++ b/src/solvers/enumpoly/efgpoly.cc @@ -32,6 +32,7 @@ #include "behavextend.h" using namespace Gambit; +using namespace Gambit::Nash; namespace { @@ -155,13 +156,6 @@ std::map ToSequenceProbs(const ProblemData &p_data, const return x; } -bool ExtendsToNash(const MixedBehaviorProfile &bs) -{ - algExtendsToNash algorithm; - return algorithm.ExtendsToNash(bs, BehaviorSupportProfile(bs.GetGame()), - BehaviorSupportProfile(bs.GetGame())); -} - std::list> SolveSupport(const BehaviorSupportProfile &p_support, bool &p_isSingular) { @@ -191,7 +185,8 @@ std::list> SolveSupport(const BehaviorSupportProfil std::list> solutions; for (auto root : quickie.RootList()) { MixedBehaviorProfile sol(data.sfg.ToMixedBehaviorProfile(ToSequenceProbs(data, root))); - if (ExtendsToNash(sol)) { + if (ExtendsToNash(sol, BehaviorSupportProfile(sol.GetGame()), + BehaviorSupportProfile(sol.GetGame()))) { solutions.push_back(sol); } } From 34d4a56f87a0c0ac3f949eb0a38d39ab9f415f60 Mon Sep 17 00:00:00 2001 From: Ted Turocy Date: Wed, 25 Sep 2024 13:08:43 +0100 Subject: [PATCH 17/99] Reimplement PNS support enumeration algorithm This reimplements our support enumeration algorithm for strategic games, using the approach proposed by Porter, Nudelman and Shoham (2004). An experimental version of this had been previously included, written by Litao Wei. This version is re-built from the ground up as a replacement. Further, we now use this approach as the approach for generating supports, and have removed the old method entirely, as there is no real benefit to the way we used to do it over what PNS propose. --- ChangeLog | 2 + Makefile.am | 2 - doc/tools.enumpoly.rst | 28 +- src/core/array.h | 8 +- src/games/stratspt.cc | 69 +--- src/games/stratspt.h | 62 +--- src/gui/gamedoc.cc | 8 +- src/solvers/enumpoly/enumpoly.h | 3 +- src/solvers/enumpoly/nfghs.cc | 456 -------------------------- src/solvers/enumpoly/nfghs.h | 109 ------ src/solvers/nashsupport/nashsupport.h | 3 +- src/solvers/nashsupport/nfgsupport.cc | 280 ++++++++++++---- src/tools/enumpoly/enumpoly.cc | 29 +- 13 files changed, 266 insertions(+), 793 deletions(-) delete mode 100644 src/solvers/enumpoly/nfghs.cc delete mode 100644 src/solvers/enumpoly/nfghs.h diff --git a/ChangeLog b/ChangeLog index 6c726bd79..1984305c3 100644 --- a/ChangeLog +++ b/ChangeLog @@ -17,6 +17,8 @@ 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) diff --git a/Makefile.am b/Makefile.am index 70a2a4ad3..1220aaf2b 100644 --- a/Makefile.am +++ b/Makefile.am @@ -468,8 +468,6 @@ gambit_enumpoly_SOURCES = \ src/solvers/enumpoly/ndarray.h \ src/solvers/enumpoly/odometer.cc \ src/solvers/enumpoly/odometer.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 \ diff --git a/doc/tools.enumpoly.rst b/doc/tools.enumpoly.rst index dd243ece2..79c2d7333 100644 --- a/doc/tools.enumpoly.rst +++ b/doc/tools.enumpoly.rst @@ -10,10 +10,15 @@ 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. +formed by the Nash equilibrium conditions. + +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 @@ -45,9 +50,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 @@ -75,18 +78,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 -<../contrib/games/e01.efg>`, the example in Figure 1 of Selten +Computing equilibria of the strategic game :download:`e01.nfg +<../contrib/games/n01.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 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/src/core/array.h b/src/core/array.h index e6f034c4d..c42f9a089 100644 --- a/src/core/array.h +++ b/src/core/array.h @@ -67,8 +67,8 @@ template class 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::forward_iterator_tag; + using difference_type = std::ptrdiff_t; using value_type = T; using pointer = value_type *; using reference = value_type &; @@ -94,8 +94,8 @@ template class 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::forward_iterator_tag; + using difference_type = std::ptrdiff_t; using value_type = T; using pointer = value_type *; using reference = value_type &; diff --git a/src/games/stratspt.cc b/src/games/stratspt.cc index e8acc4cf2..633c66a2b 100644 --- a/src/games/stratspt.cc +++ b/src/games/stratspt.cc @@ -273,20 +273,20 @@ bool StrategySupportProfile::IsDominated(const GameStrategy &s, bool p_strict, } } -bool StrategySupportProfile::Undominated(StrategySupportProfile &newS, int p_player, bool p_strict, - bool p_external) const +bool StrategySupportProfile::Undominated(StrategySupportProfile &newS, const GamePlayer &p_player, + bool p_strict, bool p_external) const { - Array set((p_external) ? m_nfg->GetPlayer(p_player)->NumStrategies() - : NumStrategies(p_player)); + Array set((p_external) ? p_player->NumStrategies() + : NumStrategies(p_player->GetNumber())); if (p_external) { for (int st = 1; st <= set.Length(); st++) { - set[st] = m_nfg->GetPlayer(p_player)->GetStrategy(st); + set[st] = p_player->GetStrategy(st); } } else { for (int st = 1; st <= set.Length(); st++) { - set[st] = GetStrategy(p_player, st); + set[st] = GetStrategy(p_player->GetNumber(), st); } } @@ -306,13 +306,9 @@ bool StrategySupportProfile::Undominated(StrategySupportProfile &newS, int p_pla 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'; 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; @@ -333,7 +329,6 @@ bool StrategySupportProfile::Undominated(StrategySupportProfile &newS, int p_pla for (int i = min + 1; i <= set.Length(); i++) { newS.RemoveStrategy(set[i]); } - return true; } else { @@ -345,23 +340,18 @@ StrategySupportProfile StrategySupportProfile::Undominated(bool p_strict, bool p { StrategySupportProfile newS(*this); - for (int pl = 1; pl <= m_nfg->NumPlayers(); pl++) { - Undominated(newS, pl, p_strict, p_external); + for (auto player : m_nfg->GetPlayers()) { + Undominated(newS, player, p_strict, p_external); } return newS; } StrategySupportProfile StrategySupportProfile::Undominated(bool p_strict, - const Array &players) const + const GamePlayer &p_player) 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); - } - + Undominated(newS, p_player, p_strict); return newS; } @@ -393,43 +383,4 @@ bool StrategySupportProfile::Overwhelms(const GameStrategy &s, const GameStrateg return true; } -//=========================================================================== -// class StrategySupportProfile::const_iterator -//=========================================================================== - -bool StrategySupportProfile::const_iterator::GoToNext() -{ - if (strat != support.NumStrategies(pl)) { - strat++; - return true; - } - else if (pl != support.GetGame()->NumPlayers()) { - pl++; - strat = 1; - return true; - } - else { - // The "at end" iterator points to (NumPlayers() + 1, 1) - pl++; - strat = 1; - return false; - } -} - -bool StrategySupportProfile::const_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 abdf202a2..75df1bfe1 100644 --- a/src/games/stratspt.h +++ b/src/games/stratspt.h @@ -53,7 +53,7 @@ class StrategySupportProfile { /// The index into a strategy profile for a strategy (-1 if not in support) Array m_profileIndex; - bool Undominated(StrategySupportProfile &newS, int p_player, bool p_strict, + bool Undominated(StrategySupportProfile &newS, const GamePlayer &, bool p_strict, bool p_external = false) const; public: @@ -138,70 +138,16 @@ 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; + /// Returns a copy of the support with dominated strategies of the specified player removed + StrategySupportProfile Undominated(bool p_strict, const GamePlayer &p_player) const; //@} /// @name Identification of overwhelmed strategies //@{ bool Overwhelms(const GameStrategy &s, const GameStrategy &t, bool p_strict) const; //@} - - class const_iterator { - public: - /// @name Lifecycle - //@{ - explicit const_iterator(const StrategySupportProfile &S, int p_pl = 1, int p_st = 1) - : support(S), pl(p_pl), strat(p_st) - { - } - ~const_iterator() = default; - //@} - - /// @name Operator overloading - //@{ - bool operator==(const const_iterator &other) const - { - return (support == other.support && pl == other.pl && strat == other.strat); - } - bool operator!=(const 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 - const_iterator &operator++() - { - GoToNext(); - return *this; - } - //@} - - /// @name Access to state information - //@{ - GameStrategy operator*() const { return GetStrategy(); } - 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; - }; - - const_iterator begin() const { return const_iterator(*this); } - const_iterator end() const { return const_iterator(*this, m_nfg->NumPlayers() + 1); } }; } // end namespace Gambit diff --git a/src/gui/gamedoc.cc b/src/gui/gamedoc.cc index 22b7cc72b..fda6c898f 100644 --- a/src/gui/gamedoc.cc +++ b/src/gui/gamedoc.cc @@ -155,13 +155,7 @@ 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); + Gambit::StrategySupportProfile newSupport = m_supports[m_current]->Undominated(m_strict); if (newSupport != *m_supports[m_current]) { m_supports.push_back(new Gambit::StrategySupportProfile(newSupport)); diff --git a/src/solvers/enumpoly/enumpoly.h b/src/solvers/enumpoly/enumpoly.h index 9755c6b05..ddcbb5c26 100644 --- a/src/solvers/enumpoly/enumpoly.h +++ b/src/solvers/enumpoly/enumpoly.h @@ -26,6 +26,7 @@ #define GAMBIT_SOLVERS_ENUMPOLY_ENUMPOLY_H #include "games/nash.h" +#include "solvers/nashsupport/nashsupport.h" namespace Gambit { namespace Nash { @@ -48,7 +49,7 @@ EnumPolyStrategySupportSolve(const StrategySupportProfile &support, bool &is_sin int p_stopAfter = 0); List> EnumPolyStrategySolve( - const Game &, double p_maxregret, + const Game &p_game, double p_maxregret, EnumPolyMixedStrategyObserverFunctionType p_onEquilibrium = EnumPolyNullMixedStrategyObserver, EnumPolyStrategySupportObserverFunctionType p_onSupport = EnumPolyNullStrategySupportObserver); diff --git a/src/solvers/enumpoly/nfghs.cc b/src/solvers/enumpoly/nfghs.cc deleted file mode 100644 index 9b0cfb198..000000000 --- a/src/solvers/enumpoly/nfghs.cc +++ /dev/null @@ -1,456 +0,0 @@ -// -// This file is part of Gambit -// Copyright (c) 1994-2024, Litao Wei The Gambit Project (http://www.gambit-project.org) -// -// FILE: src/tools/enumpoly/nfghs.cc -// Compute Nash equilibria via heuristic search on game supports -// (Porter, Nudelman & Shoham, 2004) -// Implemented by Litao Wei -// -// 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 -#include "gambit.h" -#include "nfghs.h" -#include "enumpoly.h" - -using namespace Nash; - -extern void PrintProfile(std::ostream &p_stream, const std::string &p_label, - const MixedStrategyProfile &p_profile); - -//--------------------------------------------------------------------------- -// gbtNfgHs: member functions -//--------------------------------------------------------------------------- - -void gbtNfgHs::Solve(const Game &game) -{ - int i; - int size, diff, maxsize, maxdiff; - bool preferBalance; - - Initialize(game); - - // first, setup default, for "automatic", or for malformed ordering argument - preferBalance = false; - if (numPlayers == 2) { - preferBalance = true; - } - // then, change if specified otherwise - if (Ordering() == "balance") { - preferBalance = true; - } - if (Ordering() == "size") { - preferBalance = false; - } - - List> solutions; - - // Iterate over possible sizes and differences of the support size profile. - // The order of iteration is determined by preferBalance. - // Diff is defined to be difference between the maximum and minimum size support - - maxsize = 0; - for (i = 1; i <= numPlayers; i++) { - maxsize += numActions[i]; - } - - maxdiff = maxActions - 1; - - size = numPlayers; - diff = 0; - while ((size <= maxsize) && (diff <= maxdiff)) { - - SolveSizeDiff(game, solutions, size, diff); - if ((m_stopAfter > 0) && (solutions.Length() >= m_stopAfter)) { - size = maxsize + 1; - diff = maxdiff + 1; - } - - if (preferBalance) { - size++; - if (size > maxsize) { - size = numPlayers; - diff++; - } - } - else { - diff++; - if (diff > maxdiff) { - diff = 0; - size++; - } - } - } - - Cleanup(game); -} - -void gbtNfgHs::Initialize(const Game &p_game) -{ - - // remove strict dominated strategies - - Array strategies_list; - - numPlayers = p_game->NumPlayers(); - numActions = p_game->NumStrategies(); - - int i; - minActions = numActions[1]; - maxActions = numActions[1]; - - for (i = 2; i <= numPlayers; i++) { - if (minActions > numActions[i]) { - minActions = numActions[i]; - } - if (maxActions < numActions[i]) { - maxActions = numActions[i]; - } - } -} - -void gbtNfgHs::Cleanup(const Game &game) {} - -gbtNfgHs::gbtNfgHs(int p_stopAfter) - : m_iteratedRemoval(true), m_removalWhenUninstantiated(1), m_ordering("automatic"), - m_stopAfter(p_stopAfter) -{ -} - -void gbtNfgHs::SolveSizeDiff(const Game &game, List> &solutions, - int size, int diff) -{ - int i, j; - Array supportSizeProfile(numPlayers); - for (i = 1; i <= numPlayers; i++) { - supportSizeProfile[i] = 1; - } - supportSizeProfile[1] = 0; - int currdiff, currsize, currminNumActions, currmaxNumActions; - currsize = numPlayers; - while ((currsize <= size) && (supportSizeProfile[numPlayers] <= numActions[numPlayers])) { - j = 1; - supportSizeProfile[j] += 1; - - currminNumActions = supportSizeProfile[1]; - currmaxNumActions = supportSizeProfile[1]; - for (i = 2; i <= numPlayers; i++) { - if (currminNumActions > supportSizeProfile[i]) { - currminNumActions = supportSizeProfile[i]; - } - if (currmaxNumActions < supportSizeProfile[i]) { - currmaxNumActions = supportSizeProfile[i]; - } - } - currdiff = currmaxNumActions - currminNumActions; - - currsize = 0; - for (i = 1; i <= numPlayers; i++) { - currsize += supportSizeProfile[i]; - } - while ((j != numPlayers) && - ((supportSizeProfile[j] == numActions[j] + 1) || (currsize > size))) { - supportSizeProfile[j] = 1; - j++; - supportSizeProfile[j] = supportSizeProfile[j] + 1; - - currminNumActions = supportSizeProfile[1]; - currmaxNumActions = supportSizeProfile[1]; - for (i = 2; i <= numPlayers; i++) { - if (currminNumActions > supportSizeProfile[i]) { - currminNumActions = supportSizeProfile[i]; - } - if (currmaxNumActions < supportSizeProfile[i]) { - currmaxNumActions = supportSizeProfile[i]; - } - } - currdiff = currmaxNumActions - currminNumActions; - - currsize = 0; - for (i = 1; i <= numPlayers; i++) { - currsize += supportSizeProfile[i]; - } - } - - if ((currdiff != diff) || (currsize != size)) { - continue; - } - if (supportSizeProfile[numPlayers] <= numActions[numPlayers]) { - SolveSupportSizeProfile(game, solutions, supportSizeProfile); - } - if (m_stopAfter > 0 && solutions.Length() >= m_stopAfter) { - return; - } - } -} - -bool gbtNfgHs::SolveSupportSizeProfile(const Game &game, - List> &solutions, - const Array &supportSizeProfile) -{ - Array> uninstantiatedSupports(numPlayers); - Array>> domains(numPlayers); - PVector playerSupport(supportSizeProfile); - - for (int i = 1; i <= numPlayers; i++) { - uninstantiatedSupports[i] = Array(); - int m = 1; - for (int j = 1; j <= supportSizeProfile[i]; j++) { - playerSupport(i, j) = m; - m++; - } - } - - for (int i = 1; i <= numPlayers; i++) { - bool success = false; - do { - Array supportBlock; - GetSupport(game, i, playerSupport.GetRow(i), supportBlock); - domains[i].push_back(supportBlock); - success = UpdatePlayerSupport(game, i, playerSupport); - } while (success); - } - return RecursiveBacktracking(game, solutions, uninstantiatedSupports, domains, 1); -} - -void gbtNfgHs::GetSupport(const Game &game, int playerIdx, const Vector &support, - Array &supportBlock) -{ - GamePlayer player = game->GetPlayer(playerIdx); - for (int i = 1; i <= support.Length(); i++) { - int strategyIdx = support[i]; - supportBlock.push_back(player->GetStrategy(strategyIdx)); - } -} - -bool gbtNfgHs::UpdatePlayerSupport(const Game &game, int playerIdx, PVector &playerSupport) -{ - Vector support = playerSupport.GetRow(playerIdx); - int lastBit = support.Length(); - while (true) { - playerSupport(playerIdx, lastBit) += 1; - int idx = lastBit; - while (idx > 1) { - if (playerSupport(playerIdx, idx) == (numActions[playerIdx] + 1)) { - playerSupport(playerIdx, idx - 1) += 1; - if (playerSupport(playerIdx, idx - 1) < numActions[playerIdx]) { - playerSupport(playerIdx, idx) = playerSupport(playerIdx, idx - 1) + 1; - } - else { - playerSupport(playerIdx, idx) = numActions[playerIdx]; - } - } - idx--; - } - if (playerSupport(playerIdx, 1) == (numActions[playerIdx] + 1)) { - return false; - } - - int maxIdx = support.Length(); - for (int i = maxIdx - 1; i >= 1; i--) { - if (playerSupport(playerIdx, i) >= playerSupport(playerIdx, maxIdx)) { - maxIdx = i; - } - } - if (maxIdx != lastBit) { - continue; - } - else { - break; - } - } - return true; -} - -bool gbtNfgHs::RecursiveBacktracking(const Game &game, - List> &solutions, - Array> &uninstantiatedSupports, - Array>> &domains, - int idxNextSupport2Instantiate) -{ - int idx = idxNextSupport2Instantiate; - if (idx == numPlayers + 1) { - return FeasibilityProgram(game, solutions, uninstantiatedSupports); - } - else { - // m_logfile << "Player " << idx << " domains length: " << domains[idx].Length() << " @@\n"; - int domainLength = domains[idx].Length(); - for (int k = 1; k <= domainLength; k++) { - uninstantiatedSupports[idx] = domains[idx][k]; - Array>> newDomains(numPlayers); - for (int ii = 1; ii <= idx; ii++) { - newDomains[ii].push_back(uninstantiatedSupports[ii]); - } - for (int ii = idx + 1; ii <= numPlayers; ii++) { - newDomains[ii] = domains[ii]; - } - bool success = true; - if (IteratedRemoval()) { - success = IteratedRemovalStrictlyDominatedStrategies(game, newDomains); - } - if (success) { - if (RecursiveBacktracking(game, solutions, uninstantiatedSupports, newDomains, idx + 1)) { - if ((m_stopAfter > 0) && (solutions.Length() >= m_stopAfter)) { - return true; - } - } - } - } - - return false; - } -} - -bool gbtNfgHs::IteratedRemovalStrictlyDominatedStrategies( - const Game &game, Array>> &domains) -{ - bool changed = false; - Array> domainStrategies(numPlayers); - - GetDomainStrategies(domains, domainStrategies); - - do { - changed = false; - for (int i = 1; i <= numPlayers; i++) { - GamePlayer player = game->GetPlayer(i); - - // construct restrict game - StrategySupportProfile dominatedGame(game); - - for (int pl = 1; pl <= numPlayers; pl++) { - if (pl != i) { - for (int st = 1; st <= game->GetPlayer(pl)->NumStrategies(); st++) { - if (!domainStrategies[pl].Contains(game->GetPlayer(pl)->GetStrategy(st))) { - dominatedGame.RemoveStrategy(game->GetPlayer(pl)->GetStrategy(st)); - } - } - } - } - // construct end - - for (int ai = 1; ai <= numActions[i]; ai++) { - - GameStrategy stra = player->GetStrategy(ai); - int straIdx = domainStrategies[i].Find(stra); - if (straIdx == 0) { - continue; - } - bool dominated = false; - for (int aj = 1; aj <= numActions[i]; aj++) { - if (aj != ai) { - if (IsConditionalDominatedBy(dominatedGame, domainStrategies, stra, - player->GetStrategy(aj), true)) { - dominated = true; - break; - } - } - } - - if (dominated) { - bool success = RemoveFromDomain(domains, domainStrategies, i, straIdx); - changed = true; - if (!success) { - return false; - } - } - } - } - } while (changed); - - return true; -} - -void gbtNfgHs::GetDomainStrategies(Array>> &domains, - Array> &domainStrategies) const -{ - - for (int i = 1; i <= numPlayers; i++) { - domainStrategies[i] = Array(); - for (int j = 1; j <= domains[i].Length(); j++) { - for (int k = 1; k <= domains[i][j].Length(); k++) { - if (domainStrategies[i].Find(domains[i][j][k]) == 0) { // no found - domainStrategies[i].push_back(domains[i][j][k]); - } - } - } - } -} - -bool gbtNfgHs::IsConditionalDominatedBy(StrategySupportProfile &dominatedGame, - Array> &domainStrategies, - const GameStrategy &strategy, - const GameStrategy &checkStrategy, bool strict) -{ - GamePlayer player = strategy->GetPlayer(); - int playerIdx = player->GetNumber(); - - int strategyIdx = strategy->GetNumber(); - int checkStrategyIdx = checkStrategy->GetNumber(); - GamePlayer dominatedPlayer = dominatedGame.GetGame()->GetPlayer(playerIdx); - GameStrategy newStrategy = dominatedPlayer->GetStrategy(strategyIdx); - GameStrategy newCheckStrategy = dominatedPlayer->GetStrategy(checkStrategyIdx); - - return dominatedGame.Dominates(newCheckStrategy, newStrategy, strict); -} - -bool gbtNfgHs::RemoveFromDomain(Array>> &domains, - Array> &domainStrategies, int player, - int removeStrategyIdx) -{ - - GameStrategy removeStrategy = domainStrategies[player][removeStrategyIdx]; - for (int j = 1; j <= domains[player].Length(); j++) { - int idx = domains[player][j].Find(removeStrategy); - if (idx > 0) { - domains[player].Remove(j); - j--; - } - } - GetDomainStrategies(domains, domainStrategies); - return (domains[player].Length() != 0); -} - -bool gbtNfgHs::FeasibilityProgram(const Game &game, List> &solutions, - Array> &uninstantiatedSupports) const -{ - StrategySupportProfile restrictedGame(game); - for (int pl = 1; pl <= numPlayers; pl++) { - for (int st = 1; st <= game->GetPlayer(pl)->NumStrategies(); st++) { - if (!uninstantiatedSupports[pl].Contains(game->GetPlayer(pl)->GetStrategy(st))) { - restrictedGame.RemoveStrategy(game->GetPlayer(pl)->GetStrategy(st)); - } - } - } - - bool is_singular; - auto newSolutions = - EnumPolyStrategySupportSolve(restrictedGame, is_singular, (m_stopAfter == 1) ? 1 : 0); - - bool gotSolutions = false; - for (auto soln : newSolutions) { - MixedStrategyProfile solutionToTest = soln.ToFullSupport(); - if (solutionToTest.GetLiapValue() < .01) { - gotSolutions = true; - PrintProfile(std::cout, "NE", solutionToTest); - solutions.push_back(solutionToTest); - if ((m_stopAfter > 0) && (solutions.Length() >= m_stopAfter)) { - return true; - } - } - } - return gotSolutions; -} diff --git a/src/solvers/enumpoly/nfghs.h b/src/solvers/enumpoly/nfghs.h deleted file mode 100644 index 630002015..000000000 --- a/src/solvers/enumpoly/nfghs.h +++ /dev/null @@ -1,109 +0,0 @@ -// -// This file is part of Gambit -// Copyright (c) 1994-2024, Litao Wei and The Gambit Project (http://www.gambit-project.org) -// -// FILE: src/tools/enumpoly/nfghs.h -// Compute Nash equilibria via heuristic search on game supports -// (Porter, Nudelman & Shoham, 2004) -// Implemented by Litao Wei -// -// 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 NFGHS_H -#define NFGHS_H - -#include "gambit.h" - -using namespace Gambit; - -class gbtNfgHs { -private: - int m_stopAfter; - bool m_iteratedRemoval; - int m_removalWhenUninstantiated; - std::string m_ordering; - - int minActions{0}; - int maxActions{0}; - int numPlayers{0}; - - Array numActions; - - void Initialize(const Game &game); - void Cleanup(const Game &game); - - void SolveSizeDiff(const Game &game, List> &solutions, int size, - int diff); - - bool SolveSupportSizeProfile(const Game &game, List> &solutions, - const Array &supportSizeProfile); - - void GetSupport(const Game &game, int playerIdx, const Vector &support, - Array &supportBlock); - - bool UpdatePlayerSupport(const Game &game, int playerIdx, PVector &playerSupport); - - bool RecursiveBacktracking(const Game &game, List> &solutions, - Array> &uninstantiatedSupports, - Array>> &domains, - int idxNextSupport2Instantiate); - - bool IteratedRemovalStrictlyDominatedStrategies(const Game &game, - Array>> &domains); - - void GetDomainStrategies(Array>> &domains, - Array> &domainStrategies) const; - - bool IsConditionalDominatedBy(StrategySupportProfile &dominatedGame, - Array> &domainStrategies, - const GameStrategy &strategy, const GameStrategy &checkStrategy, - bool strict); - - bool RemoveFromDomain(Array>> &domains, - Array> &domainStrategies, int player, - int removeStrategyIdx); - - bool FeasibilityProgram(const Game &game, List> &solutions, - Array> &uninstantiatedSupports) const; - -public: - gbtNfgHs(int = 1); - - virtual ~gbtNfgHs() = default; - - int StopAfter() const { return m_stopAfter; } - - void SetStopAfter(int p_stopAfter) { m_stopAfter = p_stopAfter; } - - bool IteratedRemoval() const { return m_iteratedRemoval; } - - void SetIteratedRemoval(bool p_iteratedRemoval) { m_iteratedRemoval = p_iteratedRemoval; } - - int RemovalWhenUninstantiated() const { return m_removalWhenUninstantiated; } - - void SetRemovalWhenUninstantiated(int p_removalWhenUninstantiated) - { - m_removalWhenUninstantiated = p_removalWhenUninstantiated; - } - - std::string Ordering() const { return m_ordering; } - - void SetOrdering(std::string p_ordering) { m_ordering = p_ordering; } - - void Solve(const Game &); -}; - -#endif // NFGHS_H diff --git a/src/solvers/nashsupport/nashsupport.h b/src/solvers/nashsupport/nashsupport.h index 60ddcb24e..79c22e732 100644 --- a/src/solvers/nashsupport/nashsupport.h +++ b/src/solvers/nashsupport/nashsupport.h @@ -32,7 +32,8 @@ class PossibleNashStrategySupportsResult { std::list m_supports; }; // Compute the set of strategy support profiles which can be the support of -// a totally-mixed Nash equilibrium. +// a totally-mixed Nash equilibrium, using the heuristic search method of +// Porter, Nudelman & Shoham (2004). std::shared_ptr PossibleNashStrategySupports(const Game &); class PossibleNashBehaviorSupportsResult { diff --git a/src/solvers/nashsupport/nfgsupport.cc b/src/solvers/nashsupport/nfgsupport.cc index ac8c59420..5c7632d49 100644 --- a/src/solvers/nashsupport/nfgsupport.cc +++ b/src/solvers/nashsupport/nfgsupport.cc @@ -1,9 +1,10 @@ // // This file is part of Gambit -// Copyright (c) 1994-2023, The Gambit Project (http://www.gambit-project.org) +// Copyright (c) 1994-2024, The Gambit Project (http://www.gambit-project.org) // -// FILE: src/tools/enumpoly/nfgensup.cc -// Enumerate undominated subsupports of a support +// FILE: src/solvers/nashsupport/nfgsupport.cc +// Generate possible Nash supports based on the heuristic search approach of +// Porter, Nudelman and Shoham (2004) // // 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,84 +21,223 @@ // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // +#include +#include +#include +#include + #include "nashsupport.h" using namespace Gambit; namespace { -// Given a candidate support profile p_candidate, check whether this -// can be the support of a totally mixed Nash equilibrium, by -// checking for weak dominations, and dominations by strategies -// that are in the game but outside the support. -bool AcceptCandidate(const StrategySupportProfile &p_support, - const StrategySupportProfile &p_candidate) +using StrategySupport = std::vector; +using CallbackFunction = std::function &)>; + +class CartesianRange { +private: + Array m_sizes; + +public: + CartesianRange(const Array &p_sizes) : m_sizes(p_sizes) {} + + class iterator { + private: + Array m_sizes; + Array m_indices; + bool m_end; + + public: + using iterator_category = std::forward_iterator_tag; + + iterator(const Array &p_sizes, bool p_end = false) + : m_sizes(p_sizes), m_indices(m_sizes.size()), m_end(p_end) + { + std::fill(m_indices.begin(), m_indices.end(), 1); + } + + const Array &operator*() const { return m_indices; } + + const Array &operator->() const { return m_indices; } + + iterator &operator++() + { + for (int i = 1; i <= m_sizes.size(); i++) { + if (++m_indices[i] <= m_sizes[i]) { + return *this; + } + m_indices[i] = 1; + } + m_end = true; + return *this; + } + + bool operator==(const iterator &it) const + { + return (m_end == it.m_end && m_sizes == it.m_sizes && m_indices == it.m_indices); + } + bool operator!=(const iterator &it) const { return !(*this == it); } + }; + + iterator begin() { return {m_sizes}; } + iterator end() { return {m_sizes, true}; } +}; + +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(); +} + +StrategySupportProfile RestrictedGame(const Game &game, const GamePlayer &player, + std::map &domainStrategies) { - StrategySupportProfile current(p_candidate); - StrategySupportProfile::const_iterator cursor(p_support); - do { - GameStrategy strat = cursor.GetStrategy(); - if (!current.Contains(strat)) { + StrategySupportProfile profile(game); + for (auto [player2, strategies] : domainStrategies) { + if (player2 == player) { continue; } - for (int j = 1; j <= strat->GetPlayer()->NumStrategies(); j++) { - GameStrategy other_strat = strat->GetPlayer()->GetStrategy(j); - if (other_strat != strat) { - if (current.Contains(other_strat) && current.Dominates(other_strat, strat, false)) { - return false; - } - current.AddStrategy(other_strat); - if (current.Dominates(other_strat, strat, false)) { - return false; - } - current.RemoveStrategy(other_strat); + for (auto strategy : player2->GetStrategies()) { + if (!contains(strategies, strategy)) { + profile.RemoveStrategy(strategy); } } - } while (cursor.GoToNext()); - return true; + } + return profile; } -void PossibleNashSupports(const StrategySupportProfile &p_support, - const StrategySupportProfile &p_current, - const StrategySupportProfile::const_iterator &cursor, - std::list &p_result) +bool AnyDominatedStrategies(const Game &game, std::map &domains) { - std::list deletion_list; - for (auto strategy : p_current) { - if (p_current.IsDominated(strategy, true)) { - if (cursor.IsSubsequentTo(strategy)) { - return; + for (auto [player, strategies] : domains) { + auto support_profile = RestrictedGame(game, player, domains); + for (auto strategy : strategies) { + if (support_profile.IsDominated(strategy, true)) { + return true; } - deletion_list.push_back(strategy); } } - if (!deletion_list.empty()) { - // There are strategies strictly dominated within the p_current support profile. - // Drop them, and try again on the support restricted to undominated strategies. - StrategySupportProfile restricted(p_current); - for (auto strategy : deletion_list) { - restricted.RemoveStrategy(strategy); + return false; +} + +class StrategySubsets { +private: + GamePlayer m_player; + size_t m_size; + +public: + class iterator { + private: + Array m_strategies; + StrategySupport m_current; + std::vector m_include; + bool m_end; + + private: + void UpdateCurrent() + { + m_current.clear(); + for (size_t i = 0; i < m_include.size(); i++) { + if (m_include[i]) { + m_current.push_back(m_strategies[i + 1]); + } + } + } + + public: + using iterator_category = std::forward_iterator_tag; + + iterator(const Array &p_strategies, size_t p_size, bool p_end = false) + : m_strategies(p_strategies), m_include(m_strategies.size()), m_end(p_end) + { + std::fill(m_include.begin(), m_include.begin() + p_size, true); + UpdateCurrent(); } - PossibleNashSupports(p_support, restricted, cursor, p_result); + + const StrategySupport &operator*() const { return m_current; } + + const StrategySupport &operator->() const { return m_current; } + + iterator &operator++() + { + m_end = !std::next_permutation(m_include.begin(), m_include.end(), + [](bool x, bool y) { return y < x; }); + UpdateCurrent(); + return *this; + } + + bool operator==(const iterator &it) const + { + return (m_end == it.m_end && m_strategies == it.m_strategies && m_include == it.m_include); + } + bool operator!=(const iterator &it) const { return !(*this == it); } + }; + + StrategySubsets(const GamePlayer &p_player, size_t p_size) : m_player(p_player), m_size(p_size) + { } - else { - // There are no strictly dominated strategies among the p_current support profile. - // This therefore could be a potential support profile. - if (AcceptCandidate(p_support, p_current)) { - p_result.push_back(p_current); + + GamePlayer GetPlayer() const { return m_player; } + + iterator begin() { return {m_player->GetStrategies(), m_size}; } + iterator end() { return {m_player->GetStrategies(), m_size, true}; } +}; + +void GenerateSupportProfiles(const Game &game, + std::map currentSupports, + std::list domains, CallbackFunction callback) +{ + auto domain = domains.front(); + domains.pop_front(); + for (auto support : domain) { + currentSupports[domain.GetPlayer()] = support; + if (AnyDominatedStrategies(game, currentSupports)) { + continue; } - StrategySupportProfile::const_iterator c_copy(cursor); - do { - GameStrategy strategy = c_copy.GetStrategy(); - if (p_current.Contains(strategy) && - p_current.NumStrategies(strategy->GetPlayer()->GetNumber()) > 1) { - StrategySupportProfile restricted(p_current); - restricted.RemoveStrategy(strategy); - PossibleNashSupports(p_support, restricted, c_copy, p_result); + if (domains.empty()) { + callback(currentSupports); + } + else { + GenerateSupportProfiles(game, currentSupports, domains, callback); + } + } +} + +/// Solve over supports with a total number of strategies `size` and a maximum difference +/// in player support size of `diff`. +void GenerateSizeDiff(const Game &game, int size, int diff, CallbackFunction callback) +{ + auto players = game->GetPlayers(); + CartesianRange range(game->NumStrategies()); + for (auto size_profile : range) { + if (*std::max_element(size_profile.cbegin(), size_profile.cend()) - + *std::min_element(size_profile.cbegin(), size_profile.cend()) != + diff || + std::accumulate(size_profile.cbegin(), size_profile.cend(), 0) != size) { + continue; + } + std::list domains; + std::transform(players.begin(), players.end(), size_profile.begin(), + std::back_inserter(domains), + [](const GamePlayer &player, size_t sz) -> StrategySubsets { + return {player, sz}; + }); + GenerateSupportProfiles(game, std::map(), domains, callback); + } +} + +StrategySupportProfile +StrategiesToSupport(const Game &p_game, const std::map &p_strategies) +{ + StrategySupportProfile support(p_game); + for (auto player : p_game->GetPlayers()) { + for (auto strategy : player->GetStrategies()) { + if (!contains(p_strategies.at(player), strategy)) { + support.RemoveStrategy(strategy); } - } while (c_copy.GoToNext()); + } } + return support; } } // end anonymous namespace @@ -106,9 +246,25 @@ std::shared_ptr PossibleNashStrategySupports(const Game &p_game) { auto result = std::make_shared(); - StrategySupportProfile support(p_game); - StrategySupportProfile sact(p_game); - StrategySupportProfile::const_iterator cursor(support); - PossibleNashSupports(support, sact, cursor, result->m_supports); + auto numActions = p_game->NumStrategies(); + + int maxsize = + std::accumulate(numActions.begin(), numActions.end(), 0) - p_game->NumPlayers() + 1; + int maxdiff = *std::max_element(numActions.cbegin(), numActions.cend()); + + bool preferBalance = p_game->NumPlayers() == 2; + Array dim(2); + dim[1] = (preferBalance) ? maxsize : maxdiff; + dim[2] = (preferBalance) ? maxdiff : maxsize; + + CartesianRange range(dim); + std::list> solutions; + for (auto x : range) { + GenerateSizeDiff(p_game, ((preferBalance) ? x[1] : x[2]) + p_game->NumPlayers() - 1, + ((preferBalance) ? x[2] : x[1]) - 1, + [p_game, result](const std::map &p_profile) { + result->m_supports.push_back(StrategiesToSupport(p_game, p_profile)); + }); + } return result; } diff --git a/src/tools/enumpoly/enumpoly.cc b/src/tools/enumpoly/enumpoly.cc index 026857818..33193f89f 100644 --- a/src/tools/enumpoly/enumpoly.cc +++ b/src/tools/enumpoly/enumpoly.cc @@ -26,7 +26,6 @@ #include #include "gambit.h" #include "solvers/enumpoly/enumpoly.h" -#include "solvers/enumpoly/nfghs.h" using namespace Gambit; using namespace Gambit::Nash; @@ -38,7 +37,6 @@ void PrintBanner(std::ostream &p_stream) { p_stream << "Compute Nash equilibria by solving polynomial systems\n"; p_stream << "Gambit version " VERSION ", Copyright (C) 1994-2024, The Gambit Project\n"; - p_stream << "Heuristic search implementation Copyright (C) 2006, Litao Wei\n"; p_stream << "This is free software, distributed under the GNU GPL\n\n"; } @@ -53,8 +51,6 @@ void PrintHelp(char *progname) std::cerr << " -d DECIMALS show equilibrium probabilities with DECIMALS digits\n"; std::cerr << " -h, --help print this help message\n"; std::cerr << " -S use strategic game\n"; - std::cerr << " -H use heuristic search method to optimize time\n"; - std::cerr << " to find first equilibrium (strategic games only)\n"; std::cerr << " -m MAXREGRET maximum regret acceptable as a proportion of range of\n"; std::cerr << " payoffs in the game\n"; std::cerr << " -q quiet mode (suppresses banner)\n"; @@ -125,7 +121,7 @@ int main(int argc, char *argv[]) opterr = 0; bool quiet = false; - bool useHeuristic = false, useStrategic = false; + bool useStrategic = false; double maxregret = 1.0e-4; int long_opt_index = 0; @@ -134,7 +130,7 @@ int main(int argc, char *argv[]) {"verbose", 0, nullptr, 'V'}, {nullptr, 0, nullptr, 0}}; int c; - while ((c = getopt_long(argc, argv, "d:hHSm:qvV", long_options, &long_opt_index)) != -1) { + while ((c = getopt_long(argc, argv, "d:hSm:qvV", long_options, &long_opt_index)) != -1) { switch (c) { case 'v': PrintBanner(std::cerr); @@ -145,9 +141,6 @@ int main(int argc, char *argv[]) case 'h': PrintHelp(argv[0]); break; - case 'H': - useHeuristic = true; - break; case 'S': useStrategic = true; break; @@ -198,18 +191,12 @@ int main(int argc, char *argv[]) } if (!game->IsTree() || useStrategic) { - if (useHeuristic) { - gbtNfgHs algorithm(0); - algorithm.Solve(game); - } - else { - EnumPolyStrategySolve( - game, maxregret, - [](const MixedStrategyProfile &eqm) { PrintProfile(std::cout, "NE", eqm); }, - [](const std::string &label, const StrategySupportProfile &support) { - PrintSupport(std::cout, label, support); - }); - } + EnumPolyStrategySolve( + game, maxregret, + [](const MixedStrategyProfile &eqm) { PrintProfile(std::cout, "NE", eqm); }, + [](const std::string &label, const StrategySupportProfile &support) { + PrintSupport(std::cout, label, support); + }); } else { EnumPolyBehaviorSolve( From b8383ec541fca456c1536276e0d0579444fa2722 Mon Sep 17 00:00:00 2001 From: Ted Turocy Date: Thu, 3 Oct 2024 16:35:10 +0100 Subject: [PATCH 18/99] Add stop_after argument to enumpoly This implements the stopAfter argument to enumpoly for both strategic and extensive games, and makes it available in the command-line tool and in pygambit. --- doc/tools.enumpoly.rst | 7 +++++ doc/tools.lcp.rst | 6 +++++ src/pygambit/gambit.pxd | 4 +-- src/pygambit/nash.pxi | 6 +++-- src/pygambit/nash.py | 46 ++++++++++++++++++++++++++------- src/solvers/enumpoly/efgpoly.cc | 17 +++++++----- src/solvers/enumpoly/enumpoly.h | 8 ++---- src/solvers/enumpoly/nfgpoly.cc | 13 ++++++---- src/tools/enumpoly/enumpoly.cc | 12 ++++++--- src/tools/lcp/lcp.cc | 2 +- 10 files changed, 85 insertions(+), 36 deletions(-) diff --git a/doc/tools.enumpoly.rst b/doc/tools.enumpoly.rst index 79c2d7333..0beefb281 100644 --- a/doc/tools.enumpoly.rst +++ b/doc/tools.enumpoly.rst @@ -67,6 +67,13 @@ singular supports. 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. diff --git a/doc/tools.lcp.rst b/doc/tools.lcp.rst index eb81d3c68..c09c6e8e6 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. diff --git a/src/pygambit/gambit.pxd b/src/pygambit/gambit.pxd index 2babfaa0e..fff824d57 100644 --- a/src/pygambit/gambit.pxd +++ b/src/pygambit/gambit.pxd @@ -474,10 +474,10 @@ cdef extern from "solvers/nashsupport/nashsupport.h": cdef extern from "solvers/enumpoly/enumpoly.h": c_List[c_MixedStrategyProfileDouble] EnumPolyStrategySolve( - c_Game, float + c_Game, int, float ) except +RuntimeError c_List[c_MixedBehaviorProfileDouble] EnumPolyBehaviorSolve( - c_Game, float + c_Game, int, float ) except +RuntimeError cdef extern from "solvers/logit/logit.h": diff --git a/src/pygambit/nash.pxi b/src/pygambit/nash.pxi index e7f9e0104..c23b77875 100644 --- a/src/pygambit/nash.pxi +++ b/src/pygambit/nash.pxi @@ -194,16 +194,18 @@ def _nashsupport_strategy_solve(game: Game) -> typing.List[StrategySupportProfil def _enumpoly_strategy_solve( game: Game, + stop_after: int, maxregret: float, ) -> typing.List[MixedStrategyProfileDouble]: - return _convert_mspd(EnumPolyStrategySolve(game.game, maxregret)) + 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, maxregret)) + return _convert_mbpd(EnumPolyBehaviorSolve(game.game, stop_after, maxregret)) def _logit_strategy_solve( diff --git a/src/pygambit/nash.py b/src/pygambit/nash.py index 788a58282..b483f7b5d 100644 --- a/src/pygambit/nash.py +++ b/src/pygambit/nash.py @@ -154,15 +154,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. @@ -179,6 +183,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: @@ -212,9 +220,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. @@ -550,42 +560,58 @@ def possible_nash_supports(game: libgbt.Game) -> list[libgbt.StrategySupportProf def enumpoly_solve( game: libgbt.Game, - maxregret: float = 1.0e-4, use_strategic: bool = False, + stop_after: int | None = None, + maxregret: float = 1.0e-4, ) -> NashComputationResult: - """Compute Nash equilibria by solving systems of polynomial equations. + """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 - 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. - Returns ------- res : NashComputationResult The result represented as a ``NashComputationResult`` object. """ + 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"maxregret must be a positive number; got {maxregret}") + raise ValueError( + f"enumpoly_solve(): maxregret must be a positive number; got {maxregret}" + ) if not game.is_tree or use_strategic: - equilibria = libgbt._enumpoly_strategy_solve(game, maxregret) + equilibria = libgbt._enumpoly_strategy_solve(game, stop_after, maxregret) else: - equilibria = libgbt._enumpoly_behavior_solve(game, maxregret) + 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={"maxregret": maxregret}, + parameters={"stop_after": stop_after, "maxregret": maxregret}, equilibria=equilibria, ) diff --git a/src/solvers/enumpoly/efgpoly.cc b/src/solvers/enumpoly/efgpoly.cc index be9dc1ab6..0e348d91a 100644 --- a/src/solvers/enumpoly/efgpoly.cc +++ b/src/solvers/enumpoly/efgpoly.cc @@ -157,7 +157,7 @@ std::map ToSequenceProbs(const ProblemData &p_data, const } std::list> SolveSupport(const BehaviorSupportProfile &p_support, - bool &p_isSingular) + bool &p_isSingular, int p_stopAfter) { ProblemData data(p_support); gPolyList equations(&data.Space, &data.Lex); @@ -169,10 +169,9 @@ std::list> SolveSupport(const BehaviorSupportProfil bottoms = 0; tops = 1; - QuikSolv quickie(equations); + QuikSolv solver(equations); try { - quickie.FindCertainNumberOfRoots(gRectangle(bottoms, tops), - std::numeric_limits::max(), 0); + solver.FindCertainNumberOfRoots({bottoms, tops}, std::numeric_limits::max(), p_stopAfter); } catch (const SingularMatrixException &) { p_isSingular = true; @@ -183,7 +182,7 @@ std::list> SolveSupport(const BehaviorSupportProfil } std::list> solutions; - for (auto root : quickie.RootList()) { + for (auto root : solver.RootList()) { MixedBehaviorProfile sol(data.sfg.ToMixedBehaviorProfile(ToSequenceProbs(data, root))); if (ExtendsToNash(sol, BehaviorSupportProfile(sol.GetGame()), BehaviorSupportProfile(sol.GetGame()))) { @@ -199,7 +198,7 @@ namespace Gambit { namespace Nash { List> -EnumPolyBehaviorSolve(const Game &p_game, double p_maxregret, +EnumPolyBehaviorSolve(const Game &p_game, int p_stopAfter, double p_maxregret, EnumPolyMixedBehaviorObserverFunctionType p_onEquilibrium, EnumPolyBehaviorSupportObserverFunctionType p_onSupport) { @@ -213,7 +212,8 @@ EnumPolyBehaviorSolve(const Game &p_game, double p_maxregret, for (auto support : possible_supports->m_supports) { p_onSupport("candidate", support); bool isSingular = false; - for (auto solution : SolveSupport(support, isSingular)) { + for (auto solution : + SolveSupport(support, isSingular, std::max(p_stopAfter - int(ret.size()), 0))) { MixedBehaviorProfile fullProfile = solution.ToFullSupport(); if (fullProfile.GetMaxRegret() < p_maxregret) { p_onEquilibrium(fullProfile); @@ -223,6 +223,9 @@ EnumPolyBehaviorSolve(const Game &p_game, double p_maxregret, if (isSingular) { p_onSupport("singular", support); } + if (p_stopAfter > 0 && ret.size() >= p_stopAfter) { + break; + } } return ret; } diff --git a/src/solvers/enumpoly/enumpoly.h b/src/solvers/enumpoly/enumpoly.h index ddcbb5c26..60c043f5b 100644 --- a/src/solvers/enumpoly/enumpoly.h +++ b/src/solvers/enumpoly/enumpoly.h @@ -44,12 +44,8 @@ inline void EnumPolyNullStrategySupportObserver(const std::string &, { } -std::list> -EnumPolyStrategySupportSolve(const StrategySupportProfile &support, bool &is_singular, - int p_stopAfter = 0); - List> EnumPolyStrategySolve( - const Game &p_game, double p_maxregret, + const Game &p_game, int p_stopAfter, double p_maxregret, EnumPolyMixedStrategyObserverFunctionType p_onEquilibrium = EnumPolyNullMixedStrategyObserver, EnumPolyStrategySupportObserverFunctionType p_onSupport = EnumPolyNullStrategySupportObserver); @@ -67,7 +63,7 @@ inline void EnumPolyNullBehaviorSupportObserver(const std::string &, } List> EnumPolyBehaviorSolve( - const Game &, double p_maxregret, + const Game &, int p_stopAfter, double p_maxregret, EnumPolyMixedBehaviorObserverFunctionType p_onEquilibrium = EnumPolyNullMixedBehaviorObserver, EnumPolyBehaviorSupportObserverFunctionType p_onSupport = EnumPolyNullBehaviorSupportObserver); diff --git a/src/solvers/enumpoly/nfgpoly.cc b/src/solvers/enumpoly/nfgpoly.cc index d9d8f3456..e725eac4b 100644 --- a/src/solvers/enumpoly/nfgpoly.cc +++ b/src/solvers/enumpoly/nfgpoly.cc @@ -107,7 +107,7 @@ namespace Nash { std::list> EnumPolyStrategySupportSolve(const StrategySupportProfile &support, bool &is_singular, - int p_stopAfter /* = 0 */) + int p_stopAfter) { gSpace Space(support.MixedProfileLength() - support.GetGame()->NumPlayers()); term_order Lex(&Space, lex); @@ -118,11 +118,10 @@ EnumPolyStrategySupportSolve(const StrategySupportProfile &support, bool &is_sin Vector bottoms(Space.Dmnsn()), tops(Space.Dmnsn()); bottoms = 0; tops = 1; - gRectangle cube(bottoms, tops); QuikSolv solver(equations); is_singular = false; try { - solver.FindCertainNumberOfRoots(cube, std::numeric_limits::max(), p_stopAfter); + solver.FindCertainNumberOfRoots({bottoms, tops}, std::numeric_limits::max(), p_stopAfter); } catch (const SingularMatrixException &) { is_singular = true; @@ -143,7 +142,7 @@ EnumPolyStrategySupportSolve(const StrategySupportProfile &support, bool &is_sin } List> -EnumPolyStrategySolve(const Game &p_game, double p_maxregret, +EnumPolyStrategySolve(const Game &p_game, int p_stopAfter, double p_maxregret, EnumPolyMixedStrategyObserverFunctionType p_onEquilibrium, EnumPolyStrategySupportObserverFunctionType p_onSupport) { @@ -157,7 +156,8 @@ EnumPolyStrategySolve(const Game &p_game, double p_maxregret, for (auto support : possible_supports->m_supports) { p_onSupport("candidate", support); bool is_singular; - for (auto solution : EnumPolyStrategySupportSolve(support, is_singular)) { + for (auto solution : EnumPolyStrategySupportSolve( + support, is_singular, std::max(p_stopAfter - int(ret.size()), 0))) { MixedStrategyProfile fullProfile = solution.ToFullSupport(); if (fullProfile.GetMaxRegret() < p_maxregret) { p_onEquilibrium(fullProfile); @@ -168,6 +168,9 @@ EnumPolyStrategySolve(const Game &p_game, double p_maxregret, if (is_singular) { p_onSupport("singular", support); } + if (p_stopAfter > 0 && ret.size() >= p_stopAfter) { + break; + } } return ret; } diff --git a/src/tools/enumpoly/enumpoly.cc b/src/tools/enumpoly/enumpoly.cc index 33193f89f..2f5242f50 100644 --- a/src/tools/enumpoly/enumpoly.cc +++ b/src/tools/enumpoly/enumpoly.cc @@ -53,6 +53,8 @@ void PrintHelp(char *progname) std::cerr << " -S use strategic game\n"; std::cerr << " -m MAXREGRET maximum regret acceptable as a proportion of range of\n"; std::cerr << " payoffs in the game\n"; + std::cerr << " -e EQA terminate after finding EQA equilibria\n"; + std::cerr << " (default is to search in all supports)\n"; std::cerr << " -q quiet mode (suppresses banner)\n"; std::cerr << " -V, --verbose verbose mode (shows supports investigated)\n"; std::cerr << " -v, --version print version information\n"; @@ -123,6 +125,7 @@ int main(int argc, char *argv[]) bool quiet = false; bool useStrategic = false; double maxregret = 1.0e-4; + int stopAfter = 0; int long_opt_index = 0; struct option long_options[] = {{"help", 0, nullptr, 'h'}, @@ -130,7 +133,7 @@ int main(int argc, char *argv[]) {"verbose", 0, nullptr, 'V'}, {nullptr, 0, nullptr, 0}}; int c; - while ((c = getopt_long(argc, argv, "d:hSm:qvV", long_options, &long_opt_index)) != -1) { + while ((c = getopt_long(argc, argv, "d:hSm:e:qvV", long_options, &long_opt_index)) != -1) { switch (c) { case 'v': PrintBanner(std::cerr); @@ -147,6 +150,9 @@ int main(int argc, char *argv[]) case 'm': maxregret = atof(optarg); break; + case 'e': + stopAfter = atoi(optarg); + break; case 'q': quiet = true; break; @@ -192,7 +198,7 @@ int main(int argc, char *argv[]) if (!game->IsTree() || useStrategic) { EnumPolyStrategySolve( - game, maxregret, + game, stopAfter, maxregret, [](const MixedStrategyProfile &eqm) { PrintProfile(std::cout, "NE", eqm); }, [](const std::string &label, const StrategySupportProfile &support) { PrintSupport(std::cout, label, support); @@ -200,7 +206,7 @@ int main(int argc, char *argv[]) } else { EnumPolyBehaviorSolve( - game, maxregret, + game, stopAfter, maxregret, [](const MixedBehaviorProfile &eqm) { PrintProfile(std::cout, "NE", eqm); }, [](const std::string &label, const BehaviorSupportProfile &support) { PrintSupport(std::cout, label, support); diff --git a/src/tools/lcp/lcp.cc b/src/tools/lcp/lcp.cc index 3d46118e0..f53f450a5 100644 --- a/src/tools/lcp/lcp.cc +++ b/src/tools/lcp/lcp.cc @@ -51,7 +51,7 @@ void PrintHelp(char *progname) std::cerr << " -S use strategic game\n"; std::cerr << " -P find only subgame-perfect equilibria\n"; std::cerr << " -e EQA terminate after finding EQA equilibria\n"; - std::cerr << " (default is to find all accessible equilbria\n"; + std::cerr << " (default is to find all accessible equilbria)\n"; std::cerr << " -r DEPTH terminate recursion at DEPTH\n"; std::cerr << " (only if number of equilibria sought is not 1)\n"; std::cerr << " -D print detailed information about equilibria\n"; From b0c3583d29e22b450672e3e6813312a9bb2c6338 Mon Sep 17 00:00:00 2001 From: Ted Turocy Date: Fri, 4 Oct 2024 15:18:46 +0100 Subject: [PATCH 19/99] Enforce expected number of outcomes or payoffs in .nfg files. Parsing of .nfg files has heretofore been somewhat tolerant. If too many payoffs or outcomes were given, extra ones were ignored silently; if not enough were given, the missing ones were set to zero. With this, checks are now done to ensure the correct number of outcomes or payoffs (as appropriate) are provided. Closes #119. --- ChangeLog | 4 +++ src/games/file.cc | 23 ++++++++++++++--- tests/test_file.py | 62 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 85 insertions(+), 4 deletions(-) diff --git a/ChangeLog b/ChangeLog index 1984305c3..3ed2b8396 100644 --- a/ChangeLog +++ b/ChangeLog @@ -22,6 +22,10 @@ - `to_arrays` converts a `Game` to a list of `numpy` arrays containing its reduced strategic form. (#461) +### Fixed +- When parsing .nfg files, check that the number of outcomes or payoffs is the expected number, + and raise an exception if not. (#119) + ## [16.1.2] - unreleased diff --git a/src/games/file.cc b/src/games/file.cc index 2606cc32d..cd1e1adfc 100644 --- a/src/games/file.cc +++ b/src/games/file.cc @@ -570,7 +570,10 @@ void ParseOutcomeBody(GameParserState &p_parser, Game &p_nfg) if (p_parser.GetCurrentToken() != TOKEN_NUMBER) { throw InvalidFileException(p_parser.CreateLineMsg("Expecting outcome index")); } - + else if (iter.AtEnd()) { + throw InvalidFileException( + p_parser.CreateLineMsg("More outcomes listed than entries in game table")); + } int outcomeId = atoi(p_parser.GetLastText().c_str()); if (outcomeId > 0) { (*iter)->SetOutcome(p_nfg->GetOutcome(outcomeId)); @@ -581,6 +584,10 @@ void ParseOutcomeBody(GameParserState &p_parser, Game &p_nfg) p_parser.GetNextToken(); iter++; } + if (!iter.AtEnd()) { + throw InvalidFileException( + p_parser.CreateLineMsg("Not enough outcomes listed to fill game table")); + } } void ParsePayoffBody(GameParserState &p_parser, Game &p_nfg) @@ -590,11 +597,15 @@ void ParsePayoffBody(GameParserState &p_parser, Game &p_nfg) int pl = 1; while (p_parser.GetCurrentToken() != TOKEN_EOF) { - if (p_parser.GetCurrentToken() == TOKEN_NUMBER) { - (*iter)->GetOutcome()->SetPayoff(pl, Number(p_parser.GetLastText())); + if (p_parser.GetCurrentToken() != TOKEN_NUMBER) { + throw InvalidFileException(p_parser.CreateLineMsg("Expecting payoff")); + } + else if (iter.AtEnd()) { + throw InvalidFileException( + p_parser.CreateLineMsg("More payoffs listed than entries in game table")); } else { - throw InvalidFileException(p_parser.CreateLineMsg("Expecting payoff")); + (*iter)->GetOutcome()->SetPayoff(pl, Number(p_parser.GetLastText())); } if (++pl > p_nfg->NumPlayers()) { @@ -603,6 +614,10 @@ void ParsePayoffBody(GameParserState &p_parser, Game &p_nfg) } p_parser.GetNextToken(); } + if (!iter.AtEnd()) { + throw InvalidFileException( + p_parser.CreateLineMsg("Not enough payoffs listed to fill game table")); + } } Game BuildNfg(GameParserState &p_parser, TableFileGame &p_data) diff --git a/tests/test_file.py b/tests/test_file.py index 89c2d9cf9..0c12c64ca 100644 --- a/tests/test_file.py +++ b/tests/test_file.py @@ -1,5 +1,7 @@ import unittest +import pytest + import pygambit @@ -107,3 +109,63 @@ def test_parse_string_removed_player(self): "Parse error in game file: line 1:73: " "Not enough players for number of strategy entries" ) + + +def test_nfg_payoffs_not_enough(): + data = """ +NFG 1 R "Selten (IJGT, 75), Figure 2, normal form" { "Player 1" "Player 2" } { 3 2 } +1 1 0 2 0 2 1 1 0 3 +""" + with pytest.raises(ValueError, match="Not enough payoffs"): + pygambit.Game.parse_game(data) + + +def test_nfg_payoffs_too_many(): + data = """ +NFG 1 R "Selten (IJGT, 75), Figure 2, normal form" { "Player 1" "Player 2" } { 3 2 } +1 1 0 2 0 2 1 1 0 3 2 0 5 1 +""" + with pytest.raises(ValueError, match="More payoffs listed"): + pygambit.Game.parse_game(data) + + +def test_nfg_outcomes_not_enough(): + data = """ +NFG 1 R "Two person 2 x 2 game with unique mixed equilibrium" { "Player 1" "Player 2" } + +{ { "1" "2" } +{ "1" "2" } +} +"" + +{ +{ "" 2, 0 } +{ "" 0, 1 } +{ "" 0, 1 } +{ "" 1, 0 } +} +1 2 3 +""" + with pytest.raises(ValueError, match="Not enough outcomes"): + pygambit.Game.parse_game(data) + + +def test_nfg_outcomes_too_many(): + data = """ +NFG 1 R "Two person 2 x 2 game with unique mixed equilibrium" { "Player 1" "Player 2" } + +{ { "1" "2" } +{ "1" "2" } +} +"" + +{ +{ "" 2, 0 } +{ "" 0, 1 } +{ "" 0, 1 } +{ "" 1, 0 } +} +1 2 3 4 2 +""" + with pytest.raises(ValueError, match="More outcomes listed"): + pygambit.Game.parse_game(data) From ad47d54434da73f007782f2394f5a70a635f4c5b Mon Sep 17 00:00:00 2001 From: Lucas Simeon Date: Wed, 19 Jun 2024 12:26:47 +0200 Subject: [PATCH 20/99] Validate `first_step` and `max_accel` parameters values in `logit_solve` and related functions Closes #459. --- src/pygambit/nash.py | 4 ++++ src/pygambit/qre.py | 10 +++++++- tests/test_nash.py | 54 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 67 insertions(+), 1 deletion(-) diff --git a/src/pygambit/nash.py b/src/pygambit/nash.py index b483f7b5d..638f336a1 100644 --- a/src/pygambit/nash.py +++ b/src/pygambit/nash.py @@ -662,6 +662,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: diff --git a/src/pygambit/qre.py b/src/pygambit/qre.py index ea33e0189..f0b0a4a8c 100644 --- a/src/pygambit/qre.py +++ b/src/pygambit/qre.py @@ -39,7 +39,11 @@ def logit_solve_branch( max_accel: float = 1.1, ): if maxregret <= 0.0: - raise ValueError("logit_solve(): maxregret argument must be positive") + 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: @@ -53,6 +57,10 @@ def logit_solve_lambda( 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: diff --git a/tests/test_nash.py b/tests/test_nash.py index 2d75d8b99..06f8b4c7b 100644 --- a/tests/test_nash.py +++ b/tests/test_nash.py @@ -7,6 +7,8 @@ import unittest +import pytest + import pygambit as gbt from . import games @@ -144,3 +146,55 @@ def test_logit_behavior(self): # g.set_outcome(g.root.children[0].children[0], win) # result = gbt.nash.logit_solve(g, use_strategic=False, maxregret=0.0001) # assert result.equilibria[0].max_regret() < 0.0001 + + +def test_logit_solve_branch_error_with_invalid_maxregret(): + game = games.read_from_file("const_sum_game.nfg") + with pytest.raises(ValueError, match="must be positive"): + gbt.qre.logit_solve_branch(game=game, maxregret=0) + with pytest.raises(ValueError, match="must be positive"): + gbt.qre.logit_solve_branch(game=game, maxregret=-0.3) + + +def test_logit_solve_branch_error_with_invalid_first_step(): + game = games.read_from_file("const_sum_game.nfg") + with pytest.raises(ValueError, match="must be positive"): + gbt.qre.logit_solve_branch(game=game, first_step=0) + with pytest.raises(ValueError, match="must be positive"): + gbt.qre.logit_solve_branch(game=game, first_step=-0.3) + + +def test_logit_solve_branch_error_with_invalid_max_accel(): + game = games.read_from_file("const_sum_game.nfg") + with pytest.raises(ValueError, match="at least 1.0"): + gbt.qre.logit_solve_branch(game=game, max_accel=0) + with pytest.raises(ValueError, match="at least 1.0"): + gbt.qre.logit_solve_branch(game=game, max_accel=0.1) + + +def test_logit_solve_branch(): + game = games.read_from_file("const_sum_game.nfg") + assert len(gbt.qre.logit_solve_branch( + game=game, maxregret=0.2, first_step=0.2, max_accel=1)) > 0 + + +def test_logit_solve_lambda_error_with_invalid_first_step(): + game = games.read_from_file("const_sum_game.nfg") + with pytest.raises(ValueError, match="must be positive"): + gbt.qre.logit_solve_lambda(game=game, lam=[1, 2, 3], first_step=0) + with pytest.raises(ValueError, match="must be positive"): + gbt.qre.logit_solve_lambda(game=game, lam=[1, 2, 3], first_step=-1) + + +def test_logit_solve_lambda_error_with_invalid_max_accel(): + game = games.read_from_file("const_sum_game.nfg") + with pytest.raises(ValueError, match="at least 1.0"): + gbt.qre.logit_solve_lambda(game=game, lam=[1, 2, 3], max_accel=0) + with pytest.raises(ValueError, match="at least 1.0"): + gbt.qre.logit_solve_lambda(game=game, lam=[1, 2, 3], max_accel=0.1) + + +def test_logit_solve_lambda(): + game = games.read_from_file("const_sum_game.nfg") + assert len(gbt.qre.logit_solve_lambda( + game=game, lam=[1, 2, 3], first_step=0.2, max_accel=1)) > 0 From cbd942f60bebb1bda07a76d23fea6d4ee53c870b Mon Sep 17 00:00:00 2001 From: Ted Turocy Date: Wed, 9 Oct 2024 17:19:40 +0100 Subject: [PATCH 21/99] Modernise and refactor reading and writing of savefiles. This does some general cleanups of routines to read and write savefiles: * Creates a standardised ReadXXXFile/WriteXXXFile set of functions. * Uses iterators to make the code, specifically the writing code, more generic. * Removes uses of Gambit containers (and manually-managed linked lists!) in favour of STL equivalents. --- src/games/file.cc | 755 +++++++++++------------------------ src/games/game.cc | 57 +-- src/games/game.h | 37 +- src/games/gameagg.cc | 2 - src/games/gameagg.h | 24 +- src/games/gamebagg.cc | 5 - src/games/gamebagg.h | 22 +- src/games/gametable.cc | 92 ++--- src/games/gametree.cc | 137 ++----- src/games/gametree.h | 3 +- src/games/stratspt.cc | 71 +--- src/games/writer.h | 40 ++ src/tools/convert/convert.cc | 3 + tests/test_file.py | 15 +- 14 files changed, 464 insertions(+), 799 deletions(-) diff --git a/src/games/file.cc b/src/games/file.cc index 6f19fce27..149c00344 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(); - - 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; - } - 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::vector m_players; - if (!player) { - return 0; - } - else { - return player->m_strategies.Length(); + size_t NumPlayers() const { return m_players.size(); } + Array NumStrategies() const + { + Array ret(m_players.size()); + std::transform(m_players.begin(), m_players.end(), ret.begin(), + [](const TableFilePlayer &player) { return player.m_strategies.size(); }); + return ret; } -} - -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++; + 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 ""; - } - 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,156 +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(); - } - } + for (auto player : players) { + p_parser.ExpectCurrentToken(TOKEN_NUMBER, "numerical payoff"); + outcome->SetPayoff(player, Number(p_parser.GetLastText())); + p_parser.AcceptNextToken(TOKEN_COMMA); } - 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")); - } - + 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")); - } - else if (iter.AtEnd()) { - throw InvalidFileException( - p_parser.CreateLineMsg("More outcomes listed than entries in game table")); - } - int outcomeId = atoi(p_parser.GetLastText().c_str()); - if (outcomeId > 0) { + for (StrategyProfileIterator iter(profile); !iter.AtEnd(); iter++) { + p_parser.ExpectCurrentToken(TOKEN_NUMBER, "outcome index"); + if (int outcomeId = std::stoi(p_parser.GetLastText())) { (*iter)->SetOutcome(p_nfg->GetOutcome(outcomeId)); } - else { - (*iter)->SetOutcome(nullptr); - } p_parser.GetNextToken(); - iter++; - } - if (!iter.AtEnd()) { - throw InvalidFileException( - p_parser.CreateLineMsg("Not enough outcomes listed to fill game table")); } } -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) { - throw InvalidFileException(p_parser.CreateLineMsg("Expecting payoff")); - } - else if (iter.AtEnd()) { - throw InvalidFileException( - p_parser.CreateLineMsg("More payoffs listed than entries in game table")); - } - else { - (*iter)->GetOutcome()->SetPayoff(pl, Number(p_parser.GetLastText())); + for (StrategyProfileIterator iter(profile); !iter.AtEnd(); iter++) { + for (auto player : p_nfg->GetPlayers()) { + p_parser.ExpectCurrentToken(TOKEN_NUMBER, "numerical payoff"); + (*iter)->GetOutcome()->SetPayoff(player, Number(p_parser.GetLastText())); + p_parser.GetNextToken(); } - - if (++pl > p_nfg->NumPlayers()) { - iter++; - pl = 1; - } - p_parser.GetNextToken(); - } - if (!iter.AtEnd()) { - throw InvalidFileException( - p_parser.CreateLineMsg("Not enough payoffs listed to fill game table")); } } -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; } @@ -659,169 +471,100 @@ 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"); + 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); - 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_node->SetOutcome(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")); - } - + p_state.ExpectNextToken(TOKEN_NUMBER, "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]; - } + GameInfoset infoset = p_treeData.m_infosetMap[0][infosetId]; - p_state.GetNextToken(); - - 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")); - } + 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_node->AppendMove(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 + // TODO: Verify actions match up to any previous specifications p_node->AppendMove(infoset); } } @@ -829,70 +572,50 @@ void ParseChanceNode(GameParserState &p_state, Game &p_game, GameNode &p_node, p_node->AppendMove(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 + p_state.ExpectNextToken(TOKEN_NUMBER, "player id"); + int playerId = std::stoi(p_state.GetLastText()); 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, "infoset id"); + int infosetId = std::stoi(p_state.GetLastText()); + GameInfoset infoset = p_treeData.m_infosetMap[playerId][infosetId]; - p_state.GetNextToken(); - - if (p_state.GetCurrentToken() == TOKEN_TEXT) { + if (p_state.GetNextToken() == TOKEN_TEXT) { // Information set data is specified - List actions; + std::list action_labels; 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_node->AppendMove(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 { @@ -904,31 +627,24 @@ void ParsePersonalNode(GameParserState &p_state, Game p_game, GameNode p_node, p_node->AppendMove(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); @@ -940,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_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(); + p_state.OnParseError("Invalid type of node"); } - - ParseNode(p_state, p_game, p_game->GetRoot(), p_treeData); } } // end of anonymous namespace @@ -1022,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) { @@ -1034,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 1bd60a4e5..8dadcb94b 100644 --- a/src/games/game.cc +++ b/src/games/game.cc @@ -26,6 +26,7 @@ #include #include "gambit.h" +#include "writer.h" // The references to the table and tree representations violate the logic // of separating implementation types. This will be fixed when we move @@ -265,24 +266,6 @@ Array GameRep::GetStrategies() const // 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 +280,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()) << "\" { "; - for (auto player : players) { - p_file << '"' << EscapeQuotes(player->GetLabel()) << "\" "; - } - p_file << "}\n\n{ "; - + 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 << "{ "; - for (auto strategy : player->GetStrategies()) { - p_file << '"' << EscapeQuotes(strategy->GetLabel()) << "\" "; - } - p_file << "}\n"; + p_file << FormatList(player->GetStrategies(), [](const GameStrategy &s) { + return QuoteString(s->GetLabel()); + }) << std::endl; } - p_file << "}\n"; - p_file << "\"" << EscapeQuotes(m_comment) << "\"\n\n"; + p_file << "}" << std::endl; + p_file << std::quoted(GetComment()) << std::endl << std::endl; 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 << FormatList( + players, + [&iter](const GamePlayer &p) { + return lexical_cast((*iter)->GetPayoff(p)); + }, + false, false) + << std::endl; + }; } //======================================================================== diff --git a/src/games/game.h b/src/games/game.h index c89af8ed5..1fb664e5f 100644 --- a/src/games/game.h +++ b/src/games/game.h @@ -478,7 +478,10 @@ 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 = 0) const + { + throw UndefinedException(); + } /// Write the game to a file in .nfg payoff format. virtual void WriteNfgFile(std::ostream &p_stream) const; //@} @@ -638,8 +641,36 @@ inline GameStrategy GamePlayerRep::GetStrategy(int st) const 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 &); + +/// @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 diff --git a/src/games/gameagg.cc b/src/games/gameagg.cc index 089dcdc9c..fa99371a2 100644 --- a/src/games/gameagg.cc +++ b/src/games/gameagg.cc @@ -331,6 +331,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 69d7ac83b..b1b10204d 100644 --- a/src/games/gameagg.h +++ b/src/games/gameagg.h @@ -35,14 +35,11 @@ class GameAGGRep : public GameRep { 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 &); + /// Constructor + explicit GameAGGRep(std::shared_ptr); /// Destructor ~GameAGGRep() override { @@ -149,10 +146,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 &in) +{ + try { + return new GameAGGRep(agg::AGG::makeAGG(in)); + } + 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 5bf096517..31a32b307 100644 --- a/src/games/gamebagg.cc +++ b/src/games/gamebagg.cc @@ -307,9 +307,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 83bd38c98..aa6cbf475 100644 --- a/src/games/gamebagg.h +++ b/src/games/gamebagg.h @@ -36,14 +36,11 @@ class GameBAGGRep : public GameRep { 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 { @@ -154,6 +151,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/gametable.cc b/src/games/gametable.cc index 86e4e92cf..9ca347972 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 { @@ -343,24 +344,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 +355,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 std::string(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; } //------------------------------------------------------------------------ diff --git a/src/games/gametree.cc b/src/games/gametree.cc index 2cefa4bfb..a54b1bf5f 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 { @@ -921,121 +922,59 @@ 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(), [n](const GameAction &a) { + return QuoteString(a->GetLabel()) + " " + std::string(a->GetInfoset()->GetActionProb(a)); + }); } else { - f << "0\n"; + f << FormatList(n->GetInfoset()->GetActions(), + [n](const GameAction &a) { return QuoteString(a->GetLabel()); }); } - return; + f << ' '; } - - if (n->GetInfoset()->IsChanceInfoset()) { - f << "c \""; - } - 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 std::string(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 diff --git a/src/games/gametree.h b/src/games/gametree.h index 39ef5a909..5d0e66f63 100644 --- a/src/games/gametree.h +++ b/src/games/gametree.h @@ -271,8 +271,7 @@ class GameTreeRep : public GameExplicitRep { /// @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 = 0) const override; void WriteNfgFile(std::ostream &) const override; //@} diff --git a/src/games/stratspt.cc b/src/games/stratspt.cc index 633c66a2b..7835a04bc 100644 --- a/src/games/stratspt.cc +++ b/src/games/stratspt.cc @@ -97,60 +97,31 @@ bool StrategySupportProfile::IsSubsetOf(const StrategySupportProfile &p_support) 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()) << "\" "; + 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 << "}" << std::endl; + p_file << std::quoted(m_nfg->GetComment()) << std::endl << std::endl; - 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"; - } - - p_file << '\n'; + for (StrategyProfileIterator iter(*this); !iter.AtEnd(); iter++) { + p_file << FormatList( + players, + [&iter](const GamePlayer &p) { + return lexical_cast((*iter)->GetPayoff(p)); + }, + false, false) + << std::endl; + }; } //--------------------------------------------------------------------------- diff --git a/src/games/writer.h b/src/games/writer.h index 546d50c0b..a502eb2a2 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 /// diff --git a/src/tools/convert/convert.cc b/src/tools/convert/convert.cc index c5bc084f7..2bdf6144b 100644 --- a/src/tools/convert/convert.cc +++ b/src/tools/convert/convert.cc @@ -126,6 +126,9 @@ int main(int argc, char *argv[]) try { Gambit::Game game = Gambit::ReadGame(*input_stream); + game->WriteNfgFile(std::cout); + exit(0); + if (rowPlayer < 1 || rowPlayer > game->NumPlayers()) { std::cerr << argv[0] << ": Player " << rowPlayer << " does not exist.\n"; return 1; diff --git a/tests/test_file.py b/tests/test_file.py index 11432939c..8a0d0e910 100644 --- a/tests/test_file.py +++ b/tests/test_file.py @@ -70,7 +70,7 @@ def test_parse_string_removed_player(self): pygambit.Game.parse_game(ft) self.assertEqual( str(e.exception), - "Parse error in game file: line 5:26: Expecting '}' after outcome" + "Parse error in game file: line 5:26: Expected '}'" ) def test_parse_string_extra_payoff(self): @@ -79,7 +79,7 @@ def test_parse_string_extra_payoff(self): pygambit.Game.parse_game(ft) self.assertEqual( str(e.exception), - "Parse error in game file: line 5:29: Expecting '}' after outcome" + "Parse error in game file: line 5:29: Expected '}'" ) def test_write_game_gte_sanity(self): @@ -112,8 +112,7 @@ def test_parse_string_removed_player(self): pygambit.Game.parse_game(ft) self.assertEqual( str(e.exception), - "Parse error in game file: line 1:73: " - "Not enough players for number of strategy entries" + "Parse error in game file: line 1:73: Expected '}'" ) @@ -122,7 +121,7 @@ def test_nfg_payoffs_not_enough(): NFG 1 R "Selten (IJGT, 75), Figure 2, normal form" { "Player 1" "Player 2" } { 3 2 } 1 1 0 2 0 2 1 1 0 3 """ - with pytest.raises(ValueError, match="Not enough payoffs"): + with pytest.raises(ValueError, match="Expected numerical payoff"): pygambit.Game.parse_game(data) @@ -131,7 +130,7 @@ def test_nfg_payoffs_too_many(): NFG 1 R "Selten (IJGT, 75), Figure 2, normal form" { "Player 1" "Player 2" } { 3 2 } 1 1 0 2 0 2 1 1 0 3 2 0 5 1 """ - with pytest.raises(ValueError, match="More payoffs listed"): + with pytest.raises(ValueError, match="end-of-file"): pygambit.Game.parse_game(data) @@ -152,7 +151,7 @@ def test_nfg_outcomes_not_enough(): } 1 2 3 """ - with pytest.raises(ValueError, match="Not enough outcomes"): + with pytest.raises(ValueError, match="Expected outcome index"): pygambit.Game.parse_game(data) @@ -173,5 +172,5 @@ def test_nfg_outcomes_too_many(): } 1 2 3 4 2 """ - with pytest.raises(ValueError, match="More outcomes listed"): + with pytest.raises(ValueError, match="end-of-file"): pygambit.Game.parse_game(data) From d7d6c0fdf5b978ee0962f0c96d7fa05a4c2062ad Mon Sep 17 00:00:00 2001 From: Ted Turocy Date: Wed, 16 Oct 2024 13:46:40 +0100 Subject: [PATCH 22/99] Implement PHCpack-based backend for solving polynomial systems. This extends `enumpoly_solve` with the option to use `PHCpack` [1]_ to solve the systems of polynomial equations. Closes #165. .. [1] https://homepages.math.uic.edu/~jan/PHCpack/phcpack.html --- ChangeLog | 3 + Makefile.am | 2 - configure.ac | 5 +- contrib/Makefile.am | 23 -- contrib/scripts/Makefile.am | 25 -- contrib/scripts/enumpoly/Makefile.am | 15 - contrib/scripts/enumpoly/README | 111 ----- contrib/scripts/enumpoly/countsupport.py | 40 -- contrib/scripts/enumpoly/enumpoly.py | 501 ----------------------- contrib/scripts/enumpoly/phc.py | 143 ------- contrib/scripts/enumpoly/setup.py | 9 - src/pygambit/gambit.pxd | 1 + src/pygambit/nash.pxi | 2 +- src/pygambit/nash.py | 28 ++ src/pygambit/nashphc.py | 278 +++++++++++++ src/pygambit/stratspt.pxi | 3 + 16 files changed, 315 insertions(+), 874 deletions(-) delete mode 100644 contrib/Makefile.am delete mode 100644 contrib/scripts/Makefile.am delete mode 100644 contrib/scripts/enumpoly/Makefile.am delete mode 100644 contrib/scripts/enumpoly/README delete mode 100644 contrib/scripts/enumpoly/countsupport.py delete mode 100644 contrib/scripts/enumpoly/enumpoly.py delete mode 100644 contrib/scripts/enumpoly/phc.py delete mode 100644 contrib/scripts/enumpoly/setup.py create mode 100644 src/pygambit/nashphc.py diff --git a/ChangeLog b/ChangeLog index c90a392d8..a22a64330 100644 --- a/ChangeLog +++ b/ChangeLog @@ -21,6 +21,9 @@ 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) ### Fixed - When parsing .nfg files, check that the number of outcomes or payoffs is the expected number, diff --git a/Makefile.am b/Makefile.am index 1220aaf2b..df00ce485 100644 --- a/Makefile.am +++ b/Makefile.am @@ -20,8 +20,6 @@ ## Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. ## -SUBDIRS = contrib - ACLOCAL_AMFLAGS = -I m4 EXTRA_DIST = \ diff --git a/configure.ac b/configure.ac index 1ef411049..814755b1f 100644 --- a/configure.ac +++ b/configure.ac @@ -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/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/src/pygambit/gambit.pxd b/src/pygambit/gambit.pxd index fff824d57..8aaa6d2fc 100644 --- a/src/pygambit/gambit.pxd +++ b/src/pygambit/gambit.pxd @@ -344,6 +344,7 @@ cdef extern from "games/stratspt.h": bool RemoveStrategy(c_GameStrategy) except + c_GameStrategy GetStrategy(int, int) except +IndexError bool Contains(c_GameStrategy) except + + bool IsDominated(c_GameStrategy, bool, bool) except + c_StrategySupportProfile Undominated(bool, bool) # except + doesn't compile c_MixedStrategyProfileDouble NewMixedStrategyProfileDouble \ "NewMixedStrategyProfile"() except + diff --git a/src/pygambit/nash.pxi b/src/pygambit/nash.pxi index c23b77875..90f547334 100644 --- a/src/pygambit/nash.pxi +++ b/src/pygambit/nash.pxi @@ -186,7 +186,7 @@ def _nashsupport_strategy_solve(game: Game) -> typing.List[StrategySupportProfil ret = [] result = PossibleNashStrategySupports(game.game) for c_support in make_list_of_pointer(deref(result).m_supports): - support = StrategySupportProfile(list(game.strategies), game) + support = StrategySupportProfile(game, list(game.strategies)) support.support.reset(c_support) ret.append(support) return ret diff --git a/src/pygambit/nash.py b/src/pygambit/nash.py index 638f336a1..84a5ede22 100644 --- a/src/pygambit/nash.py +++ b/src/pygambit/nash.py @@ -26,9 +26,12 @@ from __future__ import annotations import dataclasses +import pathlib import pygambit.gambit as libgbt +from . import nashphc + MixedStrategyEquilibriumSet = list[libgbt.MixedStrategyProfile] MixedBehaviorEquilibriumSet = list[libgbt.MixedBehaviorProfile] @@ -563,6 +566,7 @@ def enumpoly_solve( 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 @@ -586,10 +590,17 @@ def enumpoly_solve( 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 [1]_ 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. + + .. [1] https://homepages.math.uic.edu/~jan/PHCpack/phcpack.html """ if stop_after is None: stop_after = 0 @@ -602,6 +613,23 @@ def enumpoly_solve( 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: diff --git a/src/pygambit/nashphc.py b/src/pygambit/nashphc.py new file mode 100644 index 000000000..86e4efdee --- /dev/null +++ b/src/pygambit/nashphc.py @@ -0,0 +1,278 @@ +"""Enumerate Nash equilibria by solving systems of polynomial equations using PHCpack. +""" + +import contextlib +import itertools +import pathlib +import string +import subprocess +import sys +import tempfile +import typing + +import pygambit as gbt + + +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 + + +@contextlib.contextmanager +def _make_temporary(content: typing.Optional[str] = None) -> pathlib.Path: + """Context manager to create a temporary file containing `content', and + provide the path to the temporary file. + + If `content' is none, the temporary file is created and then deleted, while + returning the filename, for another process then to write to that file + (under the assumption that it is extremely unlikely that another program + will try to write to that same tempfile name). + """ + with tempfile.NamedTemporaryFile("w", delete=(content is None)) as f: + if content: + f.write(content) + filepath = pathlib.Path(f.name) + try: + yield filepath + finally: + filepath.unlink(missing_ok=True) + + +def _run_phc(phcpack_path: typing.Union[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 ( + _make_temporary(f"{len(equations)}\n" + ";\n".join(equations) + ";\n\n\n") as infn, + _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 _get_strategies(support: gbt.StrategySupportProfile, player: gbt.Player) -> list[gbt.Strategy]: + return [s for s in player.strategies if s in support] + + +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 = _get_strategies(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] = 0.0 + profile[_get_strategies(support, player)[0]] = 1.0 + return profile + + +def _solve_support(support: gbt.StrategySupportProfile, + phcpack_path: typing.Union[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: typing.Union[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 main(): + game = gbt.Game.parse_game(sys.stdin.read()) + phcpack_solve(game, maxregret=1.0e-6) + + +if __name__ == "__main__": + main() diff --git a/src/pygambit/stratspt.pxi b/src/pygambit/stratspt.pxi index 7daaf3f50..28ce63a63 100644 --- a/src/pygambit/stratspt.pxi +++ b/src/pygambit/stratspt.pxi @@ -253,6 +253,9 @@ class StrategySupportProfile: """ return Game.parse_game(WriteGame(deref(self.support)).decode("ascii")) + def is_dominated(self, strategy: Strategy, strict: bool, external: bool = False) -> bool: + return deref(self.support).IsDominated(strategy.strategy, strict, external) + def _undominated_strategies_solve( profile: StrategySupportProfile, strict: bool, external: bool From 1d5c3c4b475eba136baac499c7beef380f422dd9 Mon Sep 17 00:00:00 2001 From: Ted Turocy Date: Fri, 18 Oct 2024 13:48:53 +0100 Subject: [PATCH 23/99] Remove integrated lrslib and call lrsnash as external tool. This removes the very dated implementation of `lrslib` previously embedded, and instead allows for calls to `lrsnash` as an external program. --- ChangeLog | 5 + Makefile.am | 11 - doc/pygambit.user.rst | 33 + doc/tools.enumpoly.rst | 2 +- doc/tools.rst | 1 + setup.py | 6 +- src/pygambit/nash.pxi | 4 - src/pygambit/nash.py | 41 +- src/pygambit/nashlrs.py | 60 + src/pygambit/nashphc.py | 35 +- src/pygambit/util.py | 24 + src/solvers/enummixed/enummixed.h | 21 - src/solvers/enummixed/lrsenum.cc | 947 ----- src/solvers/lrs/lrslib.c | 5620 ----------------------------- src/solvers/lrs/lrslib.h | 354 -- src/solvers/lrs/lrsmp.c | 1078 ------ src/solvers/lrs/lrsmp.h | 232 -- src/solvers/lrs/lrsnashlib.c | 1195 ------ src/solvers/lrs/lrsnashlib.h | 68 - src/tools/enummixed/enummixed.cc | 16 +- 20 files changed, 166 insertions(+), 9587 deletions(-) create mode 100644 src/pygambit/nashlrs.py create mode 100644 src/pygambit/util.py delete mode 100644 src/solvers/enummixed/lrsenum.cc delete mode 100644 src/solvers/lrs/lrslib.c delete mode 100644 src/solvers/lrs/lrslib.h delete mode 100644 src/solvers/lrs/lrsmp.c delete mode 100644 src/solvers/lrs/lrsmp.h delete mode 100644 src/solvers/lrs/lrsnashlib.c delete mode 100644 src/solvers/lrs/lrsnashlib.h diff --git a/ChangeLog b/ChangeLog index a22a64330..97ace4941 100644 --- a/ChangeLog +++ b/ChangeLog @@ -25,6 +25,11 @@ `enumpoly_solve` based on an implementation formerly in the `contrib` section of the repository. (#165) +### 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) diff --git a/Makefile.am b/Makefile.am index df00ce485..00e65a661 100644 --- a/Makefile.am +++ b/Makefile.am @@ -346,14 +346,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 \ @@ -403,10 +395,8 @@ 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/enummixed/enummixed.cc @@ -416,7 +406,6 @@ gambit_nashsupport_SOURCES = \ src/solvers/nashsupport/nfgsupport.cc \ src/solvers/nashsupport/nashsupport.h -# For enumpoly, sources starting in 'pel' are from Pelican. gambit_enumpoly_SOURCES = \ ${core_SOURCES} ${game_SOURCES} ${gambit_nashsupport_SOURCES} \ src/solvers/enumpoly/gameseq.cc \ diff --git a/doc/pygambit.user.rst b/doc/pygambit.user.rst index e444f45eb..34a2e196d 100644 --- a/doc/pygambit.user.rst +++ b/doc/pygambit.user.rst @@ -797,3 +797,36 @@ data passed are a :py:class:`.MixedBehaviorProfile`, and will return a .. [#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/tools.enumpoly.rst b/doc/tools.enumpoly.rst index 0beefb281..5921b6478 100644 --- a/doc/tools.enumpoly.rst +++ b/doc/tools.enumpoly.rst @@ -86,7 +86,7 @@ singular supports. default, no information about supports is printed. Computing equilibria of the strategic game :download:`e01.nfg -<../contrib/games/n01.efg>`, the example in Figure 1 of Selten +<../contrib/games/e01.efg>`, the example in Figure 1 of Selten (International Journal of Game Theory, 1975) sometimes called "Selten's horse":: 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/setup.py b/setup.py index 94559c59d..301f56837 100644 --- a/setup.py +++ b/setup.py @@ -26,10 +26,6 @@ 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" @@ -144,7 +140,7 @@ def readme(): ], libraries=[cppgambit_bimatrix, cppgambit_liap, cppgambit_logit, cppgambit_simpdiv, cppgambit_gtracer, cppgambit_enumpoly, - cppgambit_games, cppgambit_core, lrslib], + cppgambit_games, cppgambit_core], package_dir={"": "src"}, packages=["pygambit"], ext_modules=Cython.Build.cythonize(libgambit, diff --git a/src/pygambit/nash.pxi b/src/pygambit/nash.pxi index 90f547334..6e63c149a 100644 --- a/src/pygambit/nash.pxi +++ b/src/pygambit/nash.pxi @@ -91,10 +91,6 @@ def _enummixed_strategy_solve_rational(game: Game) -> typing.List[MixedStrategyP return _convert_mspr(EnumMixedStrategySolveRational(game.game)) -def _enummixed_strategy_solve_lrs(game: Game) -> typing.List[MixedStrategyProfileRational]: - return _convert_mspr(EnumMixedStrategySolveLrs(game.game)) - - def _lcp_behavior_solve_double( game: Game, stop_after: int, max_depth: int ) -> typing.List[MixedBehaviorProfileDouble]: diff --git a/src/pygambit/nash.py b/src/pygambit/nash.py index 84a5ede22..743a9f821 100644 --- a/src/pygambit/nash.py +++ b/src/pygambit/nash.py @@ -30,7 +30,7 @@ import pygambit.gambit as libgbt -from . import nashphc +from . import nashlrs, nashphc MixedStrategyEquilibriumSet = list[libgbt.MixedStrategyProfile] MixedBehaviorEquilibriumSet = list[libgbt.MixedBehaviorProfile] @@ -103,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. @@ -112,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 ------- @@ -127,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 ) @@ -566,7 +583,7 @@ def enumpoly_solve( use_strategic: bool = False, stop_after: int | None = None, maxregret: float = 1.0e-4, - phcpack_path: pathlib.Path | str | None = None, + 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 @@ -591,7 +608,7 @@ def enumpoly_solve( difference of the maximum and minimum payoffs of the game phcpack_path : str or pathlib.Path, optional - If specified, use PHCpack [1]_ to solve the systems of equations. + 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. @@ -600,7 +617,9 @@ def enumpoly_solve( res : NashComputationResult The result represented as a ``NashComputationResult`` object. - .. [1] https://homepages.math.uic.edu/~jan/PHCpack/phcpack.html + Notes + ----- + PHCpack is available at https://homepages.math.uic.edu/~jan/PHCpack/phcpack.html """ if stop_after is None: stop_after = 0 diff --git a/src/pygambit/nashlrs.py b/src/pygambit/nashlrs.py new file mode 100644 index 000000000..d711d44d7 --- /dev/null +++ b/src/pygambit/nashlrs.py @@ -0,0 +1,60 @@ +"""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 main(): + game = gbt.Game.parse_game(sys.stdin.read()) + 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 index 86e4efdee..98e36f332 100644 --- a/src/pygambit/nashphc.py +++ b/src/pygambit/nashphc.py @@ -1,16 +1,18 @@ """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 tempfile import typing import pygambit as gbt +import pygambit.util as util def _process_phc_output(output: str) -> list[dict]: @@ -71,27 +73,7 @@ def _process_phc_output(output: str) -> list[dict]: return solutions -@contextlib.contextmanager -def _make_temporary(content: typing.Optional[str] = None) -> pathlib.Path: - """Context manager to create a temporary file containing `content', and - provide the path to the temporary file. - - If `content' is none, the temporary file is created and then deleted, while - returning the filename, for another process then to write to that file - (under the assumption that it is extremely unlikely that another program - will try to write to that same tempfile name). - """ - with tempfile.NamedTemporaryFile("w", delete=(content is None)) as f: - if content: - f.write(content) - filepath = pathlib.Path(f.name) - try: - yield filepath - finally: - filepath.unlink(missing_ok=True) - - -def _run_phc(phcpack_path: typing.Union[pathlib.Path, str], equations: list[str]) -> list[dict]: +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. @@ -123,8 +105,9 @@ def _run_phc(phcpack_path: typing.Union[pathlib.Path, str], equations: list[str] - `res' """ with ( - _make_temporary(f"{len(equations)}\n" + ";\n".join(equations) + ";\n\n\n") as infn, - _make_temporary() as outfn + 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: @@ -230,7 +213,7 @@ def _profile_from_support(support: gbt.StrategySupportProfile) -> gbt.MixedStrat def _solve_support(support: gbt.StrategySupportProfile, - phcpack_path: typing.Union[pathlib.Path, str], + phcpack_path: pathlib.Path | str, maxregret: float, negtol: float, onsupport=lambda x, label: None, @@ -259,7 +242,7 @@ def _solve_support(support: gbt.StrategySupportProfile, return profiles -def phcpack_solve(game: gbt.Game, phcpack_path: typing.Union[pathlib.Path, str], +def phcpack_solve(game: gbt.Game, phcpack_path: pathlib.Path | str, maxregret: float) -> list[gbt.MixedStrategyProfileDouble]: negtol = 1.0e-6 return [ diff --git a/src/pygambit/util.py b/src/pygambit/util.py new file mode 100644 index 000000000..714f90fdd --- /dev/null +++ b/src/pygambit/util.py @@ -0,0 +1,24 @@ +import contextlib +import pathlib +import tempfile +import typing + + +@contextlib.contextmanager +def make_temporary(content: typing.Optional[str] = None) -> pathlib.Path: + """Context manager to create a temporary file containing `content', and + provide the path to the temporary file. + + If `content' is none, the temporary file is created and then deleted, while + returning the filename, for another process then to write to that file + (under the assumption that it is extremely unlikely that another program + will try to write to that same tempfile name). + """ + with tempfile.NamedTemporaryFile("w", delete=(content is None)) as f: + if content: + f.write(content) + filepath = pathlib.Path(f.name) + try: + yield filepath + finally: + filepath.unlink(missing_ok=True) diff --git a/src/solvers/enummixed/enummixed.h b/src/solvers/enummixed/enummixed.h index b77f0bbc2..5d058b095 100644 --- a/src/solvers/enummixed/enummixed.h +++ b/src/solvers/enummixed/enummixed.h @@ -92,27 +92,6 @@ inline List> EnumMixedStrategySolveRational(const return EnumMixedStrategySolver().Solve(p_game); } -// -// Enumerate all mixed-strategy Nash equilibria of a two-player game -// using the lrslib backend. -// -class EnumMixedLrsStrategySolver : public StrategySolver { -public: - explicit EnumMixedLrsStrategySolver( - std::shared_ptr> p_onEquilibrium = nullptr) - : StrategySolver(p_onEquilibrium) - { - } - ~EnumMixedLrsStrategySolver() override = default; - - List> Solve(const Game &p_game) const override; -}; - -inline List> EnumMixedStrategySolveLrs(const Game &p_game) -{ - return EnumMixedLrsStrategySolver().Solve(p_game); -} - } // namespace Nash } // end namespace Gambit diff --git a/src/solvers/enummixed/lrsenum.cc b/src/solvers/enummixed/lrsenum.cc deleted file mode 100644 index de16a5ca4..000000000 --- a/src/solvers/enummixed/lrsenum.cc +++ /dev/null @@ -1,947 +0,0 @@ -// -// This file is part of Gambit -// Copyright (c) 1994-2024, The Gambit Project (http://www.gambit-project.org) -// -// FILE: library/src/enummixed/lrsnash.cc -// Compute Nash equilibria via enumerating extreme points using lrslib -// -// Based on the implementation in lrslib 6.2, which is -// Copyright (c) 1995-2016, David Avis -// -// 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. -// - -// -// This file was originally based heavily on nash.c distributed with -// lrslib 4.2b. -// I have tried where possible to maintain as much of the original, and -// make a minimal set of changes to interface with Gambit's game -// representation library. This way, hopefully, if future versions of lrslib -// fix bugs or create enhancements that are relevant, it will be fairly -// easy to port them to this program. -- TLT, 23.viii.2006 -// -// The interface appears to be stable enough, as updating to use -// lrslib 6.2 required no changes to this file! -- TLT, 6.vi.2016 -// - -#include -#include - -// The order of these next includes is important, because of macro definitions -#include "gambit.h" -#include "solvers/enummixed/enummixed.h" -using namespace Gambit; - -extern "C" { -#include "solvers/lrs/lrslib.h" -} - -namespace Gambit { -namespace Nash { - -// We encapsulate local support functions in an anonymous namespace -// rather than as private member functions of the solver class. -namespace { - -long getabasis2(lrs_dic *P, lrs_dat *Q, lrs_dic *P2orig, long order[]) - -/* Pivot Ax<=b to standard form */ -/*Try to find a starting basis by pivoting in the variables x[1]..x[d] */ -/*If there are any input linearities, these appear first in order[] */ -/* Steps: (a) Try to pivot out basic variables using order */ -/* Stop if some linearity cannot be made to leave basis */ -/* (b) Permanently remove the cobasic indices of linearities */ -/* (c) If some decision variable cobasic, it is a linearity, */ -/* and will be removed. */ - -{ - long i, j, k; - /* assign local variables to structures */ - lrs_mp_matrix A = P->A; - long *B = P->B; - long *C = P->C; - long *Row = P->Row; - long *Col = P->Col; - long *linearity = Q->linearity; - long *redundcol = Q->redundcol; - long m, d, nlinearity; - long nredundcol = 0L; /* will be calculated here */ - - static long firsttime = TRUE; - static long *linindex; - - m = P->m; - d = P->d; - nlinearity = Q->nlinearity; - - if (firsttime) { - firsttime = FALSE; - linindex = reinterpret_cast(calloc((m + d + 2), sizeof(long))); - } - else /* after first time we update the change in linearities from the last time, saving many - pivots */ - { - for (i = 1; i <= m + d; i++) { - linindex[i] = FALSE; - } - if (Q->debug) { - fprintf(lrs_ofp, "\nlindex ="); - } - for (i = 0; i < nlinearity; i++) { - linindex[d + linearity[i]] = TRUE; - if (Q->debug) { - fprintf(lrs_ofp, " %ld", d + linearity[i]); - } - } - - for (i = 1; i <= m; i++) { - if (linindex[B[i]]) /* pivot out unwanted linearities */ - { - k = 0; - while (k < d && (linindex[C[k]] || zero(A[Row[i]][Col[k]]))) { - k++; - } - - if (k < d) { - j = i; /* note this index changes in update, cannot use i!)*/ - - if (C[k] > B[j]) { /* decrease i or we may skip a linearity */ - i--; - } - pivot(P, Q, j, k); - update(P, Q, &j, &k); - } - else if (Q->debug || Q->verbose) { - fprintf(lrs_ofp, "\n*Couldn't remove linearity i=%ld B[i]=%ld", i, B[i]); - } - /* this is not necessarily an error, eg. two identical rows/cols in payoff matrix */ - } - } - goto hotstart; - } - - /* standard lrs processing is done on only the first call to getabasis2 */ - - if (Q->debug) { - fprintf(lrs_ofp, "\ngetabasis from inequalities given in order"); - for (i = 0; i < m; i++) { - fprintf(lrs_ofp, " %ld", order[i]); - } - } - for (j = 0; j < m; j++) { - i = 0; - while (i <= m && B[i] != d + order[j]) { - i++; /* find leaving basis index i */ - } - if (j < nlinearity && i > m) /* cannot pivot linearity to cobasis */ - { - if (Q->debug) { - printA(P, Q); - } -#ifndef LRS_QUIET - fprintf(lrs_ofp, "\nCannot find linearity in the basis"); -#endif - return FALSE; - } - if (i <= m) { /* try to do a pivot */ - k = 0; - while (C[k] <= d && zero(A[Row[i]][Col[k]])) { - k++; - } - - if (C[k] <= d) { - pivot(P, Q, i, k); - update(P, Q, &i, &k); - } - else if (j < nlinearity) { /* cannot pivot linearity to cobasis */ - if (zero(A[Row[i]][0])) { -#ifndef LRS_QUIET - fprintf(lrs_ofp, "\n*Input linearity in row %ld is redundant--skipped\n", order[j]); -#endif - linearity[j] = 0; - } - else { - if (Q->debug) { - printA(P, Q); - } - if (Q->verbose) { - fprintf(lrs_ofp, "\nInconsistent linearities"); - } - return FALSE; - } - } /* end if j < nlinearity */ - - } /* end of if i <= m .... */ - } /* end of for */ - - /* update linearity array to get rid of redundancies */ - i = 0; - k = 0; /* counters for linearities */ - while (k < nlinearity) { - while (k < nlinearity && linearity[k] == 0) { - k++; - } - if (k < nlinearity) { - linearity[i++] = linearity[k++]; - } - } - - nlinearity = i; - - /* column dependencies now can be recorded */ - /* redundcol contains input column number 0..n-1 where redundancy is */ - k = 0; - while (k < d && C[k] <= d) { - if (C[k] <= d) { /* decision variable still in cobasis */ - redundcol[nredundcol++] = C[k] - Q->hull; /* adjust for hull indices */ - } - k++; - } - - /* now we know how many decision variables remain in problem */ - Q->nredundcol = nredundcol; - Q->lastdv = d - nredundcol; - - /* if not first time we continue from here after loading dictionary */ - -hotstart: - - if (Q->debug) { - fprintf(lrs_ofp, "\nend of first phase of getabasis2: "); - fprintf(lrs_ofp, "lastdv=%ld nredundcol=%ld", Q->lastdv, Q->nredundcol); - fprintf(lrs_ofp, "\nredundant cobases:"); - for (i = 0; i < nredundcol; i++) { - fprintf(lrs_ofp, " %ld", redundcol[i]); - } - printA(P, Q); - } - - /* here we save dictionary for use next time, *before* we resize */ - - copy_dict(Q, P2orig, P); - - /* Remove linearities from cobasis for rest of computation */ - /* This is done in order so indexing is not screwed up */ - - for (i = 0; i < nlinearity; i++) { /* find cobasic index */ - k = 0; - while (k < d && C[k] != linearity[i] + d) { - k++; - } - if (k >= d) { - if (Q->debug || Q->verbose) { - fprintf(lrs_ofp, "\nCould not remove cobasic index"); - } - /* not neccesarily an error as eg., could be repeated row/col in payoff */ - } - else { - removecobasicindex(P, Q, k); - d = P->d; - } - } - if (Q->debug && nlinearity > 0) { - printA(P, Q); - } - /* set index value for first slack variable */ - - /* Check feasability */ - if (Q->givenstart) { - i = Q->lastdv + 1; - while (i <= m && !negative(A[Row[i]][0])) { - i++; - } - if (i <= m) { - fprintf(lrs_ofp, "\n*Infeasible startingcobasis - will be modified"); - } - } - return TRUE; -} /* end of getabasis2 */ - -/* In lrs_getfirstbasis and lrs_getnextbasis we use D instead of P */ -/* since the dictionary P may change, ie. &P in calling routine */ - -#define D (*D_p) - -long lrs_getfirstbasis2(lrs_dic **D_p, lrs_dat *Q, lrs_dic *P2orig, lrs_mp_matrix *Lin, - long no_output) -/* gets first basis, FALSE if none */ -/* P may get changed if lin. space Lin found */ -/* no_output is TRUE supresses output headers */ -{ - long i, j, k; - - /* assign local variables to structures */ - - lrs_mp_matrix A; - long *B, *C, /* *Row, */ *Col; - long *inequality; - long *linearity; - long hull = Q->hull; - long m, d, lastdv, nlinearity, nredundcol; - - // static long ocount=0; - - m = D->m; - d = D->d; - lastdv = Q->lastdv; - - nredundcol = 0L; /* will be set after getabasis */ - nlinearity = Q->nlinearity; /* may be reset if new linearity read */ - linearity = Q->linearity; - - A = D->A; - B = D->B; - C = D->C; - // Row = D->Row; - Col = D->Col; - inequality = Q->inequality; - - /* default is to look for starting cobasis using linearies first, then */ - /* filling in from last rows of input as necessary */ - /* linearity array is assumed sorted here */ - /* note if restart/given start inequality indices already in place */ - /* from nlinearity..d-1 */ - - for (i = 0; i < nlinearity; i++) { /* put linearities first in the order */ - inequality[i] = linearity[i]; - } - - k = 0; /* index for linearity array */ - - if (Q->givenstart) { - k = d; - } - else { - k = nlinearity; - } - for (i = m; i >= 1; i--) { - j = 0; - while (j < k && inequality[j] != i) { - j++; /* see if i is in inequality */ - } - if (j == k) { - inequality[k++] = i; - } - } - if (Q->debug) { - fprintf(lrs_ofp, "\n*Starting cobasis uses input row order"); - for (i = 0; i < m; i++) { - fprintf(lrs_ofp, " %ld", inequality[i]); - } - } - - if (!Q->maximize && !Q->minimize) { - for (j = 0; j <= d; j++) { - itomp(ZERO, A[0][j]); - } - } - - /* Now we pivot to standard form, and then find a primal feasible basis */ - /* Note these steps MUST be done, even if restarting, in order to get */ - /* the same index/inequality correspondance we had for the original prob. */ - /* The inequality array is used to give the insertion order */ - /* and is defaulted to the last d rows when givenstart=FALSE */ - - if (!getabasis2(D, Q, P2orig, inequality)) { - return FALSE; - } - - if (Q->debug) { - fprintf(lrs_ofp, "\nafter getabasis2"); - printA(D, Q); - } - nredundcol = Q->nredundcol; - lastdv = Q->lastdv; - d = D->d; - - /********************************************************************/ - /* now we start printing the output file unless no output requested */ - /********************************************************************/ - if (!no_output || Q->debug) { - fprintf(lrs_ofp, "\nV-representation"); - - /* Print linearity space */ - /* Don't print linearity if first column zero in hull computation */ - - k = 0; - - if (nredundcol > k) { - fprintf(lrs_ofp, "\nlinearity %ld ", nredundcol - k); /*adjust nredundcol for homog. */ - for (i = 1; i <= nredundcol - k; i++) { - fprintf(lrs_ofp, " %ld", i); - } - } /* end print of linearity space */ - - fprintf(lrs_ofp, "\nbegin"); - fprintf(lrs_ofp, "\n***** %ld rational", Q->n); - - } /* end of if !no_output ....... */ - - /* Reset up the inequality array to remember which index is which input inequality */ - /* inequality[B[i]-lastdv] is row number of the inequality with index B[i] */ - /* inequality[C[i]-lastdv] is row number of the inequality with index C[i] */ - - for (i = 1; i <= m; i++) { - inequality[i] = i; - } - if (nlinearity > 0) /* some cobasic indices will be removed */ - { - for (i = 0; i < nlinearity; i++) { /* remove input linearity indices */ - inequality[linearity[i]] = 0; - } - k = 1; /* counter for linearities */ - for (i = 1; i <= m - nlinearity; i++) { - while (k <= m && inequality[k] == 0) { - k++; /* skip zeroes in corr. to linearity */ - } - inequality[i] = inequality[k++]; - } - } /* end if linearity */ - if (Q->debug) { - fprintf(lrs_ofp, "\ninequality array initialization:"); - for (i = 1; i <= m - nlinearity; i++) { - fprintf(lrs_ofp, " %ld", inequality[i]); - } - } - if (nredundcol > 0) { - *Lin = lrs_alloc_mp_matrix(nredundcol, Q->n); - - for (i = 0; i < nredundcol; i++) { - if (!(Q->homogeneous && Q->hull && i == 0)) /* skip redund col 1 for homog. hull */ - { - lrs_getray(D, Q, Col[0], D->C[0] + i - hull, (*Lin)[i]); /* adjust index for deletions */ - } - - if (!removecobasicindex(D, Q, 0L)) { - return FALSE; - } - } - } /* end if nredundcol > 0 */ - - if (Q->verbose) { - fprintf(lrs_ofp, "\nNumber of pivots for starting dictionary: %ld", Q->count[3]); - // ocount=Q->count[3]; - } - - /* Do dual pivots to get primal feasibility */ - if (!primalfeasible(D, Q)) { - if (Q->verbose) { - fprintf(lrs_ofp, "\nNumber of pivots for feasible solution: %ld", Q->count[3]); - fprintf(lrs_ofp, " - No feasible solution"); - // ocount=Q->count[3]; - } - return FALSE; - } - - if (Q->verbose) { - fprintf(lrs_ofp, "\nNumber of pivots for feasible solution: %ld", Q->count[3]); - // ocount=Q->count[3]; - } - - /* Now solve LP if objective function was given */ - if (Q->maximize || Q->minimize) { - Q->unbounded = !lrs_solvelp(D, Q, Q->maximize); - - /* check to see if objective is dual degenerate */ - j = 1; - while (j <= d && !zero(A[0][j])) { - j++; - } - if (j <= d) { - Q->dualdeg = TRUE; - } - } - else - /* re-initialize cost row to -det */ - { - for (j = 1; j <= d; j++) { - copy(A[0][j], D->det); - storesign(A[0][j], NEG); - } - - itomp(ZERO, A[0][0]); /* zero optimum objective value */ - } - - /* reindex basis to 0..m if necessary */ - /* we use the fact that cobases are sorted by index value */ - if (Q->debug) { - printA(D, Q); - } - while (C[0] <= m) { - i = C[0]; - j = inequality[B[i] - lastdv]; - inequality[B[i] - lastdv] = inequality[C[0] - lastdv]; - inequality[C[0] - lastdv] = j; - C[0] = B[i]; - B[i] = i; - reorder1(C, Col, ZERO, d); - } - - if (Q->debug) { - fprintf(lrs_ofp, "\n*Inequality numbers for indices %ld .. %ld : ", lastdv + 1, m + d); - for (i = 1; i <= m - nlinearity; i++) { - fprintf(lrs_ofp, " %ld ", inequality[i]); - } - printA(D, Q); - } - - if (Q->restart) { - if (Q->debug) { - fprintf(lrs_ofp, "\nPivoting to restart co-basis"); - } - if (!restartpivots(D, Q)) { - return FALSE; - } - D->lexflag = lexmin(D, Q, ZERO); /* see if lexmin basis */ - if (Q->debug) { - printA(D, Q); - } - } - /* Check to see if necessary to resize */ - if (Q->inputd > D->d) { - *D_p = resize(D, Q); - } - - return TRUE; -} -/********* end of lrs_getfirstbasis ***************/ - -Rational to_rational(lrs_mp Nin, lrs_mp Din) -{ - lrs_mp Nt, Dt; - std::stringstream ss; - - /* reduce fraction */ - copy(Nt, Nin); - copy(Dt, Din); - reduce(Nt, Dt); - - /* format output */ - if (sign(Nin) * sign(Din) == NEG) { - ss << "-"; - } - ss << Nt[length(Nt) - 1]; - for (long i = length(Nt) - 2; i >= 1; i--) { - ss << Nt[i]; - } - if (!(Dt[0] == 2 && Dt[1] == 1)) { - /* denominator is not one */ - ss << "/"; - ss << Dt[length(Dt) - 1]; - for (long i = length(Dt) - 2; i >= 1; i--) { - ss << Dt[i]; - } - } - return lexical_cast(ss.str()); -} - -MixedStrategyProfile BuildProfile(const Game &p_game, lrs_dat *Q1, lrs_mp_vector output1, - lrs_dat *Q2, lrs_mp_vector output2) -{ - MixedStrategyProfile profile = p_game->NewMixedStrategyProfile(Rational(0)); - - long i = 1; - GamePlayer player1 = p_game->GetPlayer(1); - for (int j = 1; j <= player1->NumStrategies(); j++) { - profile[player1->GetStrategy(j)] = to_rational(output1[i++], output1[0]); - } - - i = 1; - GamePlayer player2 = p_game->GetPlayer(2); - for (int j = 1; j <= player2->NumStrategies(); j++) { - profile[player2->GetStrategy(j)] = to_rational(output2[i++], output2[0]); - } - return profile; -} - -/**********************************************************/ -/* nash2_main is a second driver used in computing nash */ -/* equilibria on a second polytope interleaved with first */ -/**********************************************************/ - -long nash2_main(lrs_dic *P1, lrs_dat *Q1, lrs_dic *P2orig, lrs_dat *Q2, lrs_mp_vector output1, - lrs_mp_vector output2, const Game &p_game, - List> &p_equilibria, - std::shared_ptr> p_onEquilibrium) - -{ - - lrs_dic *P2; /* This can get resized, cached etc. Loaded from P2orig */ - lrs_mp_matrix Lin; /* holds input linearities if any are found */ - long col; /* output column index for dictionary */ - long startcol = 0; - long prune = FALSE; /* if TRUE, getnextbasis will prune tree and backtrack */ - long nlinearity; - long *linearity; - static long firstwarning = TRUE; /* FALSE if dual deg warning for Q2 already given */ - static long firstunbounded = TRUE; /* FALSE if dual deg warning for Q2 already given */ - - long i, j; - - P2 = lrs_getdic(Q2); - copy_dict(Q2, P2, P2orig); - - /* Here we take the linearities generated by the current vertex of player 1*/ - /* and append them to the linearity in player 2's input matrix */ - /* next is the key magic linking player 1 and 2 */ - /* be careful if you mess with this! */ - linearity = Q2->linearity; - nlinearity = 0; - for (i = Q1->lastdv + 1; i <= P1->m; i++) { - if (!zero(P1->A[P1->Row[i]][0])) { - j = Q1->inequality[P1->B[i] - Q1->lastdv]; - if (j < Q1->linearity[0]) { - linearity[nlinearity++] = j; - } - } - } - /* add back in the linearity for probs summing to one */ - linearity[nlinearity++] = Q1->linearity[0]; - - /* sort linearities */ - for (i = 1; i < nlinearity; i++) { - reorder(linearity, nlinearity); - } - - Q2->nlinearity = nlinearity; - Q2->polytope = FALSE; - - // Step 2: Find a starting cobasis from default of specified order - // P2 is created to hold active dictionary data and may be cached - // Lin is created if necessary to hold linearity space - // Print linearity space if any, and retrieve output from first - // dict. - if (!lrs_getfirstbasis2(&P2, Q2, P2orig, &Lin, TRUE)) { - goto sayonara; - } - if (firstwarning && Q2->dualdeg) { - firstwarning = FALSE; - printf("\n*Warning! Dual degenerate, ouput may be incomplete"); - printf("\n*Recommendation: Add dualperturb option before maximize in second input file\n"); - } - if (firstunbounded && Q2->unbounded) { - firstunbounded = FALSE; - printf("\n*Warning! Unbounded starting dictionary for p2, output may be incomplete"); - printf("\n*Recommendation: Change/remove maximize option, or include bounds \n"); - } - - /* Pivot to a starting dictionary */ - /* There may have been column redundancy */ - /* If so the linearity space is obtained and redundant */ - /* columns are removed. User can access linearity space */ - /* from lrs_mp_matrix Lin dimensions nredundcol x d+1 */ - if (Q2->homogeneous && Q2->hull) { - startcol++; /* col zero not treated as redundant */ - } - - // Step 3: Terminate if lponly option set, otherwise initiate a reverse - // search from the starting dictionary. Get output for each new - // dict. - - /* We initiate reverse search from this dictionary */ - /* getting new dictionaries until the search is complete */ - /* User can access each output line from output which is */ - /* vertex/ray/facet from the lrs_mp_vector output */ - /* prune is TRUE if tree should be pruned at current node */ - do { - prune = lrs_checkbound(P2, Q2); - col = 0; - if (!prune && lrs_getsolution(P2, Q2, output2, col)) { - p_equilibria.push_back(BuildProfile(p_game, Q1, output1, Q2, output2)); - p_onEquilibrium->Render(p_equilibria.back()); - } - } while (lrs_getnextbasis(&P2, Q2, prune)); - -sayonara: - lrs_free_dic(P2, Q2); - return 0; -} -/*********************************************/ -/* end of nash2_main */ -/*********************************************/ - -} // end anonymous namespace - -class LrsData { -public: - lrs_dat *Q1{nullptr}, *Q2{nullptr}; /* structure for holding static problem data */ - lrs_dic *P1{nullptr}, *P2{nullptr}; /* structure for holding current dictionary and indices */ - - explicit LrsData(const Game &p_game); - ~LrsData(); - -private: - /// @name Fill in representation of game - /// - /// These functions convert the game to the tableau representation - /// for use by lrslib. - /// - ///@{ - /// Build the H-representation for player p1 - static void BuildRep(lrs_dic *P, lrs_dat *Q, const Game &p_game, int p1); - static void FillNonnegativityRows(lrs_dic *P, lrs_dat *Q, int firstRow, int lastRow, int n); - static void FillConstraintRows(lrs_dic *P, lrs_dat *Q, const Game &p_game, int p1, int p2, - int firstRow); - static void FillLinearityRow(lrs_dic *P, lrs_dat *Q, int m, int n); - ///@} -}; - -LrsData::LrsData(const Game &p_game) -{ - if (!lrs_init("")) { - throw Exception("Error in initializing lrslib"); - } - - /* allocate and init structure for static problem data */ - Q1 = lrs_alloc_dat("LRS globals"); - if (Q1 == nullptr) { - throw Exception("Error in allocating lrslib data"); - } - Q1->nash = TRUE; - Q1->n = p_game->GetPlayer(1)->GetStrategies().size() + 2; - Q1->m = p_game->MixedProfileLength() + 1; - - P1 = lrs_alloc_dic(Q1); - if (P1 == nullptr) { - throw Exception("Error in allocating lrslib data"); - } - - /* allocate and init structure for player 2's problem data */ - Q2 = lrs_alloc_dat("LRS globals"); - if (Q2 == nullptr) { - throw Exception("Error in allocating lrslib data"); - } - Q2->nash = TRUE; - Q2->n = p_game->GetPlayer(2)->GetStrategies().size() + 2; - Q2->m = p_game->MixedProfileLength() + 1; - - P2 = lrs_alloc_dic(Q2); - if (P2 == nullptr) { - throw Exception("Error in allocating lrslib data"); - } - - BuildRep(P1, Q1, p_game, 2); - BuildRep(P2, Q2, p_game, 1); -} - -LrsData::~LrsData() -{ - if (P2) { - lrs_free_dic(P2, Q2); - P2 = nullptr; - } - if (Q2) { - lrs_free_dat(Q2); - Q2 = nullptr; - } - if (P1) { - lrs_free_dic(P1, Q1); - P1 = nullptr; - } - if (Q1) { - lrs_free_dat(Q1); - Q1 = nullptr; - } - - lrs_close(""); -} - -void LrsData::BuildRep(lrs_dic *P, lrs_dat *Q, const Game &p_game, int p1) -{ - int p2 = 3 - p1; - long m = Q->m; /* number of inequalities */ - long n = Q->n; - - if (p1 == 1) { - FillConstraintRows(P, Q, p_game, p1, p2, 1); - FillNonnegativityRows(P, Q, p_game->GetPlayer(p1)->GetStrategies().size() + 1, - p_game->MixedProfileLength(), n); - } - else { - FillNonnegativityRows(P, Q, 1, p_game->GetPlayer(p2)->GetStrategies().size(), n); - FillConstraintRows(P, Q, p_game, p1, p2, p_game->GetPlayer(p2)->GetStrategies().size() + 1); - } - FillLinearityRow(P, Q, m, n); -} - -void LrsData::FillNonnegativityRows(lrs_dic *P, lrs_dat *Q, int firstRow, int lastRow, int n) -{ - const int MAXCOL = 1000; /* maximum number of columns */ - long num[MAXCOL], den[MAXCOL]; - - for (long row = firstRow; row <= lastRow; row++) { - num[0] = 0; - den[0] = 1; - - for (long col = 1; col < n; col++) { - num[col] = (row - firstRow + 1 == col) ? 1 : 0; - den[col] = 1; - } - - lrs_set_row(P, Q, row, num, den, GE); - } -} - -void LrsData::FillConstraintRows(lrs_dic *P, lrs_dat *Q, const Game &p_game, int p1, int p2, - int firstRow) -{ - const int MAXCOL = 1000; /* maximum number of columns */ - long num[MAXCOL], den[MAXCOL]; - - Rational min = p_game->GetMinPayoff() - Rational(1); - PureStrategyProfile cont = p_game->NewPureStrategyProfile(); - - for (size_t row = firstRow; row < firstRow + p_game->GetPlayer(p1)->GetStrategies().size(); - row++) { - num[0] = 0; - den[0] = 1; - - cont->SetStrategy(p_game->GetPlayer(p1)->GetStrategies()[row - firstRow + 1]); - - for (size_t st = 1; st <= p_game->GetPlayer(p2)->GetStrategies().size(); st++) { - cont->SetStrategy(p_game->GetPlayer(p2)->GetStrategies()[st]); - Rational x = cont->GetPayoff(p1) - min; - - num[st] = -x.numerator().as_long(); - den[st] = x.denominator().as_long(); - } - - num[p_game->GetPlayer(p2)->GetStrategies().size() + 1] = 1; - den[p_game->GetPlayer(p2)->GetStrategies().size() + 1] = 1; - lrs_set_row(P, Q, row, num, den, GE); - } -} - -void LrsData::FillLinearityRow(lrs_dic *P, lrs_dat *Q, int m, int n) -{ - const int MAXCOL = 1000; /* maximum number of columns */ - long num[MAXCOL], den[MAXCOL]; - - num[0] = -1; - den[0] = 1; - - for (int i = 1; i < n - 1; i++) { - num[i] = 1; - den[i] = 1; - } - - num[n - 1] = 0; - den[n - 1] = 1; - - lrs_set_row(P, Q, m, num, den, EQ); -} - -List> EnumMixedLrsStrategySolver::Solve(const Game &p_game) const -{ - if (p_game->NumPlayers() != 2) { - throw UndefinedException("Method only valid for two-player games."); - } - if (!p_game->IsPerfectRecall()) { - throw UndefinedException( - "Computing equilibria of games with imperfect recall is not supported."); - } - - lrs_mp_vector output1; /* holds one line of output; ray,vertex,facet,linearity */ - lrs_mp_vector output2; /* holds one line of output; ray,vertex,facet,linearity */ - lrs_mp_matrix Lin; /* holds input linearities if any are found */ - - lrs_dic *P2orig; /* we will save player 2's dictionary in getabasis */ - - long col; /* output column index for dictionary */ - long startcol = 0; - long prune = FALSE; /* if TRUE, getnextbasis will prune tree and backtrack */ - - List> equilibria; - - // Step 1: Set up the problem - LrsData data(p_game); - - output1 = lrs_alloc_mp_vector(data.Q1->n + - data.Q1->m); /* output holds one line of output from dictionary */ - output2 = lrs_alloc_mp_vector(data.Q2->n + - data.Q2->m); /* output holds one line of output from dictionary */ - - P2orig = lrs_getdic(data.Q2); /* allocate and initialize lrs_dic */ - if (P2orig == nullptr) { - throw Exception("Error in allocating lrslib data"); - } - copy_dict(data.Q2, P2orig, data.P2); - - // Step 2: Find a starting cobasis from default of specified order - // P1 is created to hold active dictionary data and may be cached - // Lin is created if necessary to hold linearity space - // Print linearity space if any, and retrieve output from first - // dict. - if (!lrs_getfirstbasis(&data.P1, data.Q1, &Lin, TRUE)) { - throw Exception("Error in getting first basis in lrslib."); - } - - if (data.Q1->dualdeg) { - printf("\n*Warning! Dual degenerate, ouput may be incomplete"); - printf("\n*Recommendation: Add dualperturb option before maximize in first input file\n"); - } - - if (data.Q1->unbounded) { - printf("\n*Warning! Unbounded starting dictionary for p1, output may be incomplete"); - printf("\n*Recommendation: Change/remove maximize option, or include bounds \n"); - } - - /* Pivot to a starting dictionary */ - /* There may have been column redundancy */ - /* If so the linearity space is obtained and redundant */ - /* columns are removed. User can access linearity space */ - /* from lrs_mp_matrix Lin dimensions nredundcol x d+1 */ - if (data.Q1->homogeneous && data.Q1->hull) { - startcol++; /* col zero not treated as redundant */ - } - - for (col = startcol; col < data.Q1->nredundcol; col++) { - /* print linearity space */ - /* Array Lin[][] holds the coeffs. */ - lrs_printoutput(data.Q1, Lin[col]); - } - - // - // Step 3: Terminate if lponly option set, otherwise initiate a reverse - // search from the starting dictionary. Get output for each new - // dict. - - /* We initiate reverse search from this dictionary */ - /* getting new dictionaries until the search is complete */ - /* User can access each output line from output which is */ - /* vertex/ray/facet from the lrs_mp_vector output */ - /* prune is TRUE if tree should be pruned at current node */ - do { - // FIXME: In some circumstances, especially the Python extension, - // this algorithm runs very slowly. However, adding any sort - // of output call makes it run very quickly. (!) (?) - // This needs to be chased up further. - prune = lrs_checkbound(data.P1, data.Q1); - if (!prune && lrs_getsolution(data.P1, data.Q1, output1, col)) { - nash2_main(data.P1, data.Q1, P2orig, data.Q2, output1, output2, p_game, equilibria, - m_onEquilibrium); - } - } while (lrs_getnextbasis(&data.P1, data.Q1, prune)); - - lrs_clear_mp_vector(output1, data.Q1->m + data.Q1->n); - lrs_clear_mp_vector(output2, data.Q2->m + data.Q2->n); - - data.Q2->Qhead = data.P2; /* reset this or you crash free_dic */ - - return equilibria; -} - -} // namespace Nash -} // end namespace Gambit diff --git a/src/solvers/lrs/lrslib.c b/src/solvers/lrs/lrslib.c deleted file mode 100644 index 4686484ff..000000000 --- a/src/solvers/lrs/lrslib.c +++ /dev/null @@ -1,5620 +0,0 @@ -/* lrslib.c library code for lrs */ - -/* modified by Gary Roumanis for multithread plrs compatability */ -/* truncate needs mod to supress last pivot */ -/* need to add a test for non-degenerate pivot step in reverse I guess */ -/* Copyright: David Avis 2005,2011 avis@cs.mcgill.ca */ - -/* 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., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. - */ - -#include -#include -#include "lrslib.h" - -/* Globals; these need to be here, rather than lrslib.h, so they are - not multiply defined. */ - -FILE *lrs_cfp; /* output file for checkpoint information */ -FILE *lrs_ifp; /* input file pointer */ -FILE *lrs_ofp; /* output file pointer */ - -static unsigned long dict_count, dict_limit, cache_tries, cache_misses; - -/* Variables and functions global to this file only */ -static long lrs_checkpoint_seconds = 0; - -static long lrs_global_count = 0; /* Track how many lrs_dat records are - allocated */ - -static lrs_dat_p *lrs_global_list[MAX_LRS_GLOBALS + 1]; - -static lrs_dic *new_lrs_dic(long m, long d, long m_A); - -static void cache_dict(lrs_dic **D_p, lrs_dat *global, long i, long j); -static long check_cache(lrs_dic **D_p, lrs_dat *global, long *i_p, long *j_p); -static void save_basis(lrs_dic *D, lrs_dat *Q); - -static void lrs_dump_state(); - -static void pushQ(lrs_dat *global, long m, long d, long m_A); - -#ifdef TIMES -static void ptimes(); -static double get_time(); -#endif - -/*******************************/ -/* signals handling */ -/*******************************/ -#ifdef SIGNALS -static void checkpoint(); -static void die_gracefully(); -static void setup_signals(); -static void timecheck(); -#endif - -/*******************************/ -/* functions for external use */ -/*******************************/ - -/*******************************************************/ -/* lrs_main is driver for lrs.c does H/V enumeration */ -/* showing function calls intended for public use */ -/*******************************************************/ -long lrs_main(int argc, char *argv[]) - -{ - - lrs_dic *P; /* structure for holding current dictionary and indices */ - lrs_dat *Q; /* structure for holding static problem data */ - - lrs_mp_vector output; /* holds one line of output; ray,vertex,facet,linearity */ - lrs_mp_matrix Lin; /* holds input linearities if any are found */ - long col; /* output column index for dictionary */ - long startcol = 0; - long prune = FALSE; /* if TRUE, getnextbasis will prune tree and backtrack */ - - /* global variables lrs_ifp and lrs_ofp are file pointers for input and output */ - /* they default to stdin and stdout, but may be overidden by command line parms. */ - - /*************************************************** - Step 0: - Do some global initialization that should only be done once, - no matter how many lrs_dat records are allocated. db - - ***************************************************/ - -#ifdef PLRS - if (!lrs_mp_init(ZERO, stdin, stdout)) { /* initialize arithmetic */ - exit(1); - } -#else - if (!lrs_init("\n*lrs:")) { - return 1; - } -#ifdef LRS_LOGGING - printf("\n%s", AUTHOR); -#endif // LRS_LOGGING -#endif - -/*********************************************************************************/ -/* Step 1: Allocate lrs_dat, lrs_dic and set up the problem */ -/*********************************************************************************/ -#ifdef PLRS - Q = lrs_alloc_dat(""); /* allocate and init structure for static problem data */ - - std::ifstream input_file; - input_file.open(argv[0]); /* Open input file */ - plrs_read_dat(Q, input_file); /* read first part of problem data to get dimensions and problem - type: H- or V- input representation */ - - P = lrs_alloc_dic(Q); /* allocate and initialize lrs_dic */ - if (P == NULL) { - return 1; - } - plrs_read_dic(P, Q, input_file); /* read remainder of input to setup P and Q */ -#else - Q = lrs_alloc_dat("LRS globals"); /* allocate and init structure for static problem data */ - - if (Q == NULL) { - return 1; - } - - if (!lrs_read_dat(Q, argc, argv)) { /* read first part of problem data to get dimensions */ - return 1; /* and problem type: H- or V- input representation */ - } - - P = lrs_alloc_dic(Q); /* allocate and initialize lrs_dic */ - if (P == NULL) { - return 1; - } - - if (!lrs_read_dic(P, Q)) { /* read remainder of input to setup P and Q */ - return 1; - } -#endif - - output = lrs_alloc_mp_vector(Q->n); /* output holds one line of output from dictionary */ - - /*********************************************************************************/ - /* Step 2: Find a starting cobasis from default of specified order */ - /* P is created to hold active dictionary data and may be cached */ - /* Lin is created if necessary to hold linearity space */ - /* Print linearity space if any, and retrieve output from first dict. */ - /*********************************************************************************/ - - if (!lrs_getfirstbasis(&P, Q, &Lin, FALSE)) { - return 1; - } - - /* Pivot to a starting dictionary */ - /* There may have been column redundancy */ - /* If so the linearity space is obtained and redundant */ - /* columns are removed. User can access linearity space */ - /* from lrs_mp_matrix Lin dimensions nredundcol x d+1 */ - - if (Q->homogeneous && Q->hull) { - startcol++; /* col zero not treated as redundant */ - } - - if (!Q->restart) { - for (col = startcol; col < Q->nredundcol; col++) { /* print linearity space */ - lrs_printoutput(Q, Lin[col]); /* Array Lin[][] holds the coeffs. */ - } - } - - /*********************************************************************************/ - /* Step 3: Terminate if lponly option set, otherwise initiate a reverse */ - /* search from the starting dictionary. Get output for each new dict. */ - /*********************************************************************************/ - - /* We initiate reverse search from this dictionary */ - /* getting new dictionaries until the search is complete */ - /* User can access each output line from output which is */ - /* vertex/ray/facet from the lrs_mp_vector output */ - /* prune is TRUE if tree should be pruned at current node */ - prune = lrs_checkbound(P, Q); - do { - - // 2015.6.5 after maxcobases reached, generate subtrees that have not been enumerated - - if ((Q->maxcobases > 0) && (Q->count[2] >= Q->maxcobases)) { - if (!lrs_leaf(P, Q)) { - /* do not return cobases of a leaf */ - lrs_printcobasis(P, Q, ZERO); - } - - prune = TRUE; - - } // if Q-> maxcobases... - - for (col = 0; col <= P->d; col++) { /* print output vertex/ray if any */ - if (lrs_getsolution(P, Q, output, col)) { - lrs_printoutput(Q, output); - } - } - - } while (!Q->lponly && lrs_getnextbasis(&P, Q, prune)); // do ... - - if (Q->lponly) { - lrs_lpoutput(P, Q, output); - } - else { - lrs_printtotals(P, Q); /* print final totals, including estimates */ - } - - lrs_clear_mp_vector(output, Q->n); - - /* 2015.9.16 fix memory leaks on Gcd Lcm Lin */ - if (Q->nredundcol > 0) { - lrs_clear_mp_matrix(Lin, Q->nredundcol, Q->n); - } - if (Q->runs > 0) { - free(Q->isave); - free(Q->jsave); - } - long savem = P->m; /* need this to clear Q*/ - lrs_free_dic(P, Q); /* deallocate lrs_dic */ - Q->m = savem; - - lrs_free_dat(Q); /* deallocate lrs_dat */ - -#ifndef PLRS - lrs_close("lrs:"); -#endif - - return 0; -} -/*********************************************/ -/* end of model test program for lrs library */ -/*********************************************/ - -/***********************************/ -/* PLRS */ -/***********************************/ - -#ifdef PLRS - -void plrs_read_dat(lrs_dat *Q, std::ifstream &input_file) -{ - - string line; - bool begin = false; - - if (input_file.is_open()) { - while (input_file.good()) { - getline(input_file, line); - - if (line.find("*") == 0) { - // Ignore lines starting with * - } - else if (line.find("H-representation") != string::npos) { - Q->hull = FALSE; - } - else if (line.find("hull") != string::npos || - line.find("V-representation") != string::npos) { - Q->hull = TRUE; - Q->polytope = TRUE; - } - else if (line.find("digits") != string::npos) { - long dec_digits; - istringstream ss(line); - if (!(ss >> dec_digits) && !lrs_set_digits(dec_digits)) { - printf("\nError reading digits data!\n"); - exit(1); - } - } - else if (line.find("nonnegative") != string::npos) { - Q->nonnegative = TRUE; - } - else if (line.find("linearity") != string::npos) { - // Remove the following characters - char chars[] = "linearity"; - for (unsigned int i = 0; i < sizeof(chars); ++i) { - line.erase(remove(line.begin(), line.end(), chars[i]), line.end()); - } - - plrs_readlinearity(Q, line); - } - else if (line.find("begin") != string::npos) { - begin = true; - break; - } - else { - // Q->name = line.c_str(); - } - } - - if (Q->hull) { - Q->getvolume = TRUE; - } - - if (!begin) { - printf("\nNo begin line!\n"); - fprintf(lrs_ofp, "\nNo begin line!\n"); - exit(1); - } - - getline(input_file, line); - istringstream ss(line); - string type; - - if (!(ss >> Q->m >> Q->n >> type)) { - printf("\nNo data in file!\n"); - exit(1); - } - - if (!type.find("integer") && !type.find("rational")) { - printf("\nData type must be integer or rational!\n"); - exit(1); - } - } - else { - printf("\nError reading input file!\n"); - exit(1); - } - - if (Q->m == 0) { - printf("\nNo input given!\n"); - exit(1); - } - /* inputd may be reduced in preprocessing of linearities and redund cols */ -} - -/* read constraint matrix and set up problem and dictionary */ -void plrs_read_dic(lrs_dic *P, lrs_dat *Q, std::ifstream &input_file) -{ - - lrs_mp Temp, mpone; - lrs_mp_vector oD; /* Denom for objective function */ - - long i, j; - string line; - - /* assign local variables to structures */ - - lrs_mp_matrix A; - lrs_mp_vector Gcd, Lcm; - long hull = Q->hull; - long m, d; - - lrs_alloc_mp(Temp); - lrs_alloc_mp(mpone); - A = P->A; - m = Q->m; - d = Q->inputd; - - Gcd = Q->Gcd; - Lcm = Q->Lcm; - - oD = lrs_alloc_mp_vector(d); - - itomp(ONE, mpone); - itomp(ONE, A[0][0]); - itomp(ONE, Lcm[0]); - itomp(ONE, Gcd[0]); - - for (i = 1; i <= m; i++) /* read in input matrix row by row */ - { - - itomp(ONE, Lcm[i]); /* Lcm of denominators */ - itomp(ZERO, Gcd[i]); /* Gcd of numerators */ - - if (!input_file.good()) { - printf("\nInput data incorrectly formatted\n"); - exit(1); - } - - /* allow embedded CRs in multiline input for matrix rows */ - /* there must be an easier way .... but this seems to work */ - j = hull; - while (j <= d) /* hull data copied to cols 1..d */ - { - if (!input_file.good()) { - printf("\nInput incorrectly formatted\n"); - exit(1); - } - - getline(input_file, line); - istringstream ss(line); - const char *ptr1; - int string_length; - string_length = 1; - while ((j <= d) && (string_length != 0)) { - string rat; - ss >> rat; - ptr1 = rat.c_str(); - string_length = strlen(ptr1); - if (string_length != 0) { - if (plrs_readrat(A[i][j], A[0][j], ptr1)) { - lcm(Lcm[i], A[0][j]); /* update lcm of denominators */ - } - copy(Temp, A[i][j]); - gcd(Gcd[i], Temp); /* update gcd of numerators */ - j++; - } - } - ss.clear(); - } - - if (hull) { - itomp(ZERO, A[i][0]); /*for hull, we have to append an extra column of zeroes */ - if (!one(A[i][1]) || !one(A[0][1])) { /* all rows must have a one in column one */ - Q->polytope = FALSE; - } - } - - if (!zero(A[i][hull])) { /* for H-rep, are zero in column 0 */ - Q->homogeneous = FALSE; /* for V-rep, all zero in column 1 */ - } - - storesign(Gcd[i], POS); - storesign(Lcm[i], POS); - - if (mp_greater(Gcd[i], mpone) || mp_greater(Lcm[i], mpone)) { - for (j = 0; j <= d; j++) { - divint(A[i][j], Gcd[i], Temp); /*reduce numerators by Gcd */ - mulint(Lcm[i], Temp, Temp); /*remove denominators */ - divint(Temp, A[0][j], A[i][j]); /*reduce by former denominator */ - } - } - } - - /* 2010.4.26 patch */ - if (Q->nonnegative) { /* set up Gcd and Lcm for nonexistent nongative inequalities */ - for (i = m + 1; i <= m + d; i++) { - itomp(ONE, Lcm[i]); - itomp(ONE, Gcd[i]); - } - } - - // Make new output node for nonfatal option errors - // Make stream to collect prat / pmp data - stringstream out_stream; - - if (Q->homogeneous && Q->verbose) { - out_stream << "*Input is homogeneous, column 1 not treated as redundant" << endl; - } - - while (input_file.good()) { - getline(input_file, line); - if (line.find("*") == 0) { - // Ignore lines starting with * - } - else if (line.find("startingcobasis") != string::npos) { - if (Q->nonnegative) { - out_stream << "*Starting cobasis incompatible with nonegative option:skipped" << endl; - } - else { - - Q->givenstart = TRUE; - istringstream ss(line); - // Trim first word - string str; - ss >> str; - // make string out of facts - stringstream facets; - facets << ss.rdbuf(); - - // Readfacets - plrs_readfacets(Q, Q->inequality, facets.str()); - } - } - else if (line.find("restart") != string::npos) { - - Q->restart = TRUE; - istringstream ss(line); - // Trim first word - string str; - ss >> str; - // Pipe restart data from string stream - if (Q->voronoi) { - if (!(ss >> Q->count[1] >> Q->count[0] >> Q->count[2] >> P->depth)) { - printf("\nError reading restart data!\n"); - exit(1); - } - } - else if (hull) { - if (!(ss >> Q->count[0] >> Q->count[2] >> P->depth)) { - printf("\nError reading restart data!\n"); - exit(1); - } - } - else { - if (!(ss >> Q->count[1] >> Q->count[0] >> Q->count[2] >> P->depth)) { - printf("\nError reading restart data!\n"); - exit(1); - } - } - // Store starting counts to calculate totals - for (int i = 0; i < 5; i++) { - Q->startcount[i] = Q->count[i]; - } - - // Make string out of facets - stringstream facets; - facets << ss.rdbuf(); - plrs_readfacets(Q, Q->facet, facets.str()); - } - else if (line.find("geometric") != string::npos) { - if (hull && !Q->voronoi) { - out_stream << "*Geometric option for H-representation or voronoi only, skipped" << endl; - } - else { - Q->geometric = TRUE; - } - } - else if (line.find("allbases") != string::npos) { - Q->allbases = TRUE; - } - else if (line.find("countonly") != string::npos) { - Q->countonly = TRUE; - } - else if (line.find("incidence") != string::npos) { - Q->incidence = TRUE; - } - else if (line.find("#incidence") != string::npos) { - Q->printcobasis = TRUE; - } - else if (line.find("printcobasis") != string::npos) { - istringstream ss(line); - // Trim first word - string str; - ss >> str; - if (!(ss >> Q->frequency)) { - Q->frequency = 0; - } - Q->printcobasis = TRUE; - } - else if (line.find("printslack") != string::npos) { - Q->printslack = TRUE; - } - else if (line.find("maxdepth") != string::npos) { - istringstream ss(line); - // Trim first word - string str; - ss >> str; - if (!(ss >> Q->maxdepth)) { - Q->maxdepth = 1; - } - } - else if (line.find("maxoutput") != string::npos) { - istringstream ss(line); - // Trim first word - string str; - ss >> str; - if (!(ss >> Q->maxoutput)) { - Q->maxoutput = 100; - } - } - else if (line.find("maxcobases") != string::npos) { - istringstream ss(line); - // Trim first word - string str; - ss >> str; - if (!(ss >> Q->maxcobases)) { - Q->maxcobases = 1000; - } - } - else if (line.find("lponly") != string::npos) { - printf("\nError: lponly option not supported - use lrs!\n"); - exit(1); - } - else if (line.find("mindepth") != string::npos) { - istringstream ss(line); - // Trim first word - string str; - ss >> str; - if (!(ss >> Q->mindepth)) { - Q->mindepth = 0; - } - } - else if (line.find("estimates") != string::npos) { - istringstream ss(line); - // Trim first word - string str; - ss >> str; - if (!(ss >> Q->runs)) { - Q->runs = 1; - } - } - else if (line.find("subtreesize") != string::npos) { - istringstream ss(line); - // Trim first word - string str; - ss >> str; - if (!(ss >> Q->subtreesize)) { - Q->subtreesize = MAXD; - } - } - else if (line.find("truncate") != string::npos) { - if (!hull) { - Q->truncate = TRUE; - } - else { - out_stream << "*Truncate option for H-representation only, skipped" << endl; - } - } - else if (line.find("verbose") != string::npos) { - Q->verbose = TRUE; - } - else if (line.find("bound") != string::npos) { - istringstream ss(line); - // Trim first word - string str; - ss >> str; - // get rational number - ss >> str; - plrs_readrat(Q->boundn, Q->boundd, str.c_str()); - Q->bound = TRUE; - } - else if (line.find("nonnegative") != string::npos) { - out_stream << "*Nonnegative option must come before begin line, skipped" << endl; - } - else if (line.find("seed") != string::npos) { - istringstream ss(line); - if (!(ss >> Q->seed)) { - Q->seed = 3142; - } - } - else if (line.find("voronoi") != string::npos || line.find("Voronoi") != string::npos) { - if (!hull) { - out_stream << "*voronoi requires V-representation - option skipped" << endl; - } - else { - Q->voronoi = TRUE; - Q->polytope = FALSE; - } - } - } - - if (Q->restart && Q->maxcobases > 0) { // 2015.4.3 adjust for restart - Q->maxcobases = Q->maxcobases + Q->count[2]; - } - - if (Q->incidence) { - Q->printcobasis = TRUE; - /* 2010.5.7 No need to reset this, as it may have been set by printcobasis */ - /* Q->frequency = ZERO; */ - } - - lrs_clear_mp(Temp); - lrs_clear_mp(mpone); - lrs_clear_mp_vector(oD, d); - - // post output in a nonblocking manner (a consumer thread will manage output) - post_output("options warning", out_stream.str().c_str()); -} - -/* read and check facet list for obvious errors during start/restart */ -/* this must be done after linearity option is processed!! */ -void plrs_readfacets(lrs_dat *Q, long facet[], string facets) -{ - long i, j; - /* assign local variables to structures */ - long m, d; - long *linearity = Q->linearity; - m = Q->m; - d = Q->inputd; - - istringstream ss(facets); - for (j = Q->nlinearity; j < d; j++) /* note we place these after the linearity indices */ - { - if (!(ss >> facet[j])) { - return; - } - - // fprintf (lrs_ofp, " %ld", facet[j]); - /* 2010.4.26 nonnegative option needs larger range of indices */ - if (Q->nonnegative) { - if (facet[j] < 1 || facet[j] > m + d) { - printf("\nStart/Restart cobasic indices must be in range 1 .. %ld \n", m + d); - exit(1); - } - } - if (!Q->nonnegative) { - if (facet[j] < 1 || facet[j] > m) { - printf("\nStart/Restart cobasic indices must be in range 1 .. %ld \n", m); - exit(1); - } - } - for (i = 0; i < Q->nlinearity; i++) { - if (linearity[i] == facet[j]) { - printf("\nStart/Restart cobasic indices should not include linearities\n"); - ; - exit(1); - } - } - /* bug fix 2011.8.1 reported by Steven Wu*/ - for (i = Q->nlinearity; i < j; i++) { - /* end bug fix 2011.8.1 */ - - if (facet[i] == facet[j]) { - printf("\nStart/Restart cobasic indices must be distinct\n"); - exit(1); - } - } - } -} /* end of readfacets */ - -extern int PLRS_DEBUG; -#endif - -/*******************************************************/ -/* redund_main is driver for redund.c, removes all */ -/* redundant rows from an H or V-representation */ -/* showing function calls intended for public use */ -/*******************************************************/ -long redund_main(int argc, char *argv[]) - -{ - lrs_mp_matrix Ain; /* holds a copy of the input matrix to output at the end */ - - long *redineq; /* redineq[i]=0 if ineq i non-red,1 if red,2 linearity */ - long ineq; /* input inequality number of current index */ - - lrs_dic *P; /* structure for holding current dictionary and indices */ - lrs_dat *Q; /* structure for holding static problem data */ - - lrs_mp_matrix Lin; /* holds input linearities if any are found */ - - long i, j, d, m; - long nlinearity; /* number of linearities in input file */ - long nredund; /* number of redundant rows in input file */ - long lastdv; - long debug; - long index; /* basic index for redundancy test */ - - /* global variables lrs_ifp and lrs_ofp are file pointers for input and output */ - /* they default to stdin and stdout, but may be overidden by command line parms. */ - /* Lin is global 2-d array for linearity space if it is found (redund columns) */ - - lrs_ifp = stdin; - lrs_ofp = stdout; - /*************************************************** - Step 0: - Do some global initialization that should only be done once, - no matter how many lrs_dat records are allocated. db - - ***************************************************/ - - if (!lrs_init("\n*redund:")) { - return 1; - } - - printf("\n"); - printf(AUTHOR); - - /*********************************************************************************/ - /* Step 1: Allocate lrs_dat, lrs_dic and set up the problem */ - /*********************************************************************************/ - - Q = lrs_alloc_dat("LRS globals"); /* allocate and init structure for static problem data */ - - if (Q == NULL) { - return 1; - } - - if (!lrs_read_dat(Q, argc, argv)) { /* read first part of problem data to get dimensions */ - return 1; /* and problem type: H- or V- input representation */ - } - - P = lrs_alloc_dic(Q); /* allocate and initialize lrs_dic */ - if (P == NULL) { - return 1; - } - - if (!lrs_read_dic(P, Q)) { /* read remainder of input to setup P and Q */ - return 1; - } - - /* if non-negative flag is set, non-negative constraints are not input */ - /* explicitly, and are not checked for redundancy */ - - m = P->m_A; /* number of rows of A matrix */ - d = P->d; - debug = Q->debug; - - redineq = (long *)calloc((m + 1), sizeof(long)); - Ain = lrs_alloc_mp_matrix(m, d); /* make a copy of A matrix for output later */ - - for (i = 1; i <= m; i++) { - for (j = 0; j <= d; j++) { - copy(Ain[i][j], P->A[i][j]); - } - - if (debug) { - lrs_printrow("*", Q, Ain[i], d); - } - } - - /*********************************************************************************/ - /* Step 2: Find a starting cobasis from default of specified order */ - /* Lin is created if necessary to hold linearity space */ - /*********************************************************************************/ - - if (!lrs_getfirstbasis(&P, Q, &Lin, TRUE)) { - return 1; - } - - /* Pivot to a starting dictionary */ - /* There may have been column redundancy */ - /* If so the linearity space is obtained and redundant */ - /* columns are removed. User can access linearity space */ - /* from lrs_mp_matrix Lin dimensions nredundcol x d+1 */ - - /*********************************************************************************/ - /* Step 3: Test each row of the dictionary to see if it is redundant */ - /*********************************************************************************/ - - /* note some of these may have been changed in getting initial dictionary */ - m = P->m_A; - d = P->d; - nlinearity = Q->nlinearity; - lastdv = Q->lastdv; - if (debug) { - fprintf(lrs_ofp, "\ncheckindex m=%ld, n=%ld, nlinearity=%ld lastdv=%ld", m, d, nlinearity, - lastdv); - } - - /* linearities are not considered for redundancy */ - - for (i = 0; i < nlinearity; i++) { - redineq[Q->linearity[i]] = 2L; - } - - /* rows 0..lastdv are cost, decision variables, or linearities */ - /* other rows need to be tested */ - - for (index = lastdv + 1; index <= m + d; index++) { - ineq = Q->inequality[index - lastdv]; /* the input inequality number corr. to this index */ - - redineq[ineq] = checkindex(P, Q, index); - if (debug) { - fprintf(lrs_ofp, "\ncheck index=%ld, inequality=%ld, redineq=%ld", index, ineq, - redineq[ineq]); - } - if (redineq[ineq] == ONE) { - fprintf(lrs_ofp, "\n*row %ld was redundant and removed", ineq); - fflush(lrs_ofp); - } - - } /* end for index ..... */ - - if (debug) { - fprintf(lrs_ofp, "\n*redineq:"); - for (i = 1; i <= m; i++) { - fprintf(lrs_ofp, " %ld", redineq[i]); - } - } - - if (!Q->hull) { - fprintf(lrs_ofp, "\nH-representation"); - } - else { - fprintf(lrs_ofp, "\nV-representation"); - } - - /* linearities will be printed first in output */ - - if (nlinearity > 0) { - fprintf(lrs_ofp, "\nlinearity %ld", nlinearity); - for (i = 1; i <= nlinearity; i++) { - fprintf(lrs_ofp, " %ld", i); - } - } - nredund = nlinearity; /* count number of non-redundant inequalities */ - for (i = 1; i <= m; i++) { - if (redineq[i] == 0) { - nredund++; - } - } - fprintf(lrs_ofp, "\nbegin"); - fprintf(lrs_ofp, "\n%ld %ld rational", nredund, Q->n); - - /* print the linearities first */ - - for (i = 0; i < nlinearity; i++) { - lrs_printrow("", Q, Ain[Q->linearity[i]], Q->inputd); - } - - for (i = 1; i <= m; i++) { - if (redineq[i] == 0) { - lrs_printrow("", Q, Ain[i], Q->inputd); - } - } - fprintf(lrs_ofp, "\nend"); - fprintf(lrs_ofp, "\n*Input had %ld rows and %ld columns", m, Q->n); - fprintf(lrs_ofp, ": %ld row(s) redundant", m - nredund); - - /* 2015.9.9 fix memory leak on Gcd Lcm */ - long savem = P->m; /* need this to clear Q*/ - lrs_free_dic(P, Q); /* deallocate lrs_dic */ - Q->m = savem; - - lrs_free_dat(Q); /* deallocate lrs_dat */ - - lrs_close("redund:"); - - return 0; -} -/*********************************************/ -/* end of redund.c */ -/*********************************************/ -/*******************/ -/* lrs_printoutput */ -/*******************/ -void lrs_printoutput(lrs_dat *Q, lrs_mp_vector output) -{ - - if (Q->countonly) { - return; - } - -#ifdef PLRS - // Make new output node - char *type = NULL; - - // Make stream to collect prat / pmp data - stringstream ss; - - if (Q->hull || zero(output[0])) { - /*non vertex */ - type = "ray"; - for (int i = 0; i < Q->n; i++) { - ss << pmp("", output[i]); - } - } - else { - type = "vertex"; - /* vertex */ - ss << " 1 "; - for (int i = 1; i < Q->n; i++) { - ss << prat("", output[i], output[0]); - } - } - // post output in a nonblocking manner (a consumer thread will manage output) - post_output(type, ss.str().c_str()); -#else - long i; - - fprintf(lrs_ofp, "\n"); - - if (Q->hull || zero(output[0])) /*non vertex */ - { - for (i = 0; i < Q->n; i++) { - pmp("", output[i]); - } - } - else { /* vertex */ - fprintf(lrs_ofp, " 1 "); - for (i = 1; i < Q->n; i++) { - prat("", output[i], output[0]); - } - } - fflush(lrs_ofp); -#endif -} -/**************************/ -/* end of lrs_printoutput */ -/**************************/ - -/****************/ -/* lrs_lpoutput */ -/****************/ -void lrs_lpoutput(lrs_dic *P, lrs_dat *Q, lrs_mp_vector output) -{ - -#ifndef LRS_QUIET - lrs_mp Temp1, Temp2; - long i; - - lrs_alloc_mp(Temp1); - lrs_alloc_mp(Temp2); - - fprintf(lrs_ofp, "\n*LP solution only requested"); - prat("\n\n*Objective function has value ", P->objnum, P->objden); - - fprintf(lrs_ofp, "\n\n*Primal: "); - for (i = 1; i < Q->n; i++) { - fprintf(lrs_ofp, "x_%ld=", i); - prat("", output[i], output[0]); - } - - if (Q->nlinearity > 0) { - fprintf(lrs_ofp, "\n\n*Linearities in input file - partial dual solution only"); - } - fprintf(lrs_ofp, "\n\n*Dual: "); - - for (i = 0; i < P->d; i++) { - fprintf(lrs_ofp, "y_%ld=", Q->inequality[P->C[i] - Q->lastdv]); - changesign(P->A[0][P->Col[i]]); - mulint(Q->Lcm[P->Col[i]], P->A[0][P->Col[i]], Temp1); - mulint(Q->Gcd[P->Col[i]], P->det, Temp2); - prat("", Temp1, Temp2); - changesign(P->A[0][P->Col[i]]); - } - fprintf(lrs_ofp, "\n"); - lrs_clear_mp(Temp1); - lrs_clear_mp(Temp2); -#endif -} -/***********************/ -/* end of lrs_lpoutput */ -/***********************/ -void lrs_printrow(char name[], lrs_dat *Q, lrs_mp_vector output, long rowd) -/* print a row of A matrix in output in "original" form */ -/* rowd+1 is the dimension of output vector */ -/* if input is H-rep. output[0] contains the RHS */ -/* if input is V-rep. vertices are scaled by 1/output[1] */ -{ - long i; - - fprintf(lrs_ofp, "\n%s", name); - if (!Q->hull) /* input was inequalities, print directly */ - - { - - for (i = 0; i <= rowd; i++) { - pmp("", output[i]); - } - return; - } - - /* input was vertex/ray */ - - if (zero(output[1])) /*non-vertex */ - { - for (i = 1; i <= rowd; i++) { - pmp("", output[i]); - } - } - else { /* vertex */ - fprintf(lrs_ofp, " 1 "); - for (i = 2; i <= rowd; i++) { - prat("", output[i], output[1]); - } - } - - return; - -} /* end of lrs_printrow */ - -long lrs_getsolution(lrs_dic *P, lrs_dat *Q, lrs_mp_vector output, long col) -/* check if column indexed by col in this dictionary */ -/* contains output */ -/* col=0 for vertex 1....d for ray/facet */ -{ - - long j; /* cobasic index */ - - lrs_mp_matrix A = P->A; - long *Row = P->Row; - - if (col == ZERO) { /* check for lexmin vertex */ - return lrs_getvertex(P, Q, output); - } - - /* check for rays: negative in row 0 , positive if lponly */ - - if (Q->lponly) { - if (!positive(A[0][col])) { - return FALSE; - } - } - - else if (!negative(A[0][col])) { - return FALSE; - } - - /* and non-negative for all basic non decision variables */ - - j = Q->lastdv + 1; - while (j <= P->m && !negative(A[Row[j]][col])) { - j++; - } - - if (j <= P->m) { - return FALSE; - } - - if (Q->geometric || Q->allbases || lexmin(P, Q, col) || Q->lponly) { - - return lrs_getray(P, Q, col, Q->n, output); - } - - return FALSE; /* no more output in this dictionary */ - -} /* end of lrs_getsolution */ - -long lrs_init(const char *name) /* returns TRUE if successful, else FALSE */ -{ -#ifdef LRS_LOGGING - printf("%s", name); - printf(TITLE); - printf(LRS_VERSION); - printf("("); - printf(BIT); - printf(","); - printf(ARITH); -#endif // LRS_LOGGING - if (!lrs_mp_init(ZERO, stdin, stdout)) { /* initialize arithmetic */ - return FALSE; - } -#ifdef LRS_LOGGING - printf(")"); -#endif // LRS_LOGGING - - lrs_global_count = 0; - lrs_checkpoint_seconds = 0; -#ifdef SIGNALS - setup_signals(); -#endif - return TRUE; -} - -void lrs_close(const char *name) -{ -#ifdef LRS_LOGGING - fprintf(lrs_ofp, "\n*%s", name); - fprintf(lrs_ofp, TITLE); - fprintf(lrs_ofp, LRS_VERSION); - fprintf(lrs_ofp, "("); - fprintf(lrs_ofp, BIT); - fprintf(lrs_ofp, ","); - fprintf(lrs_ofp, ARITH); - fprintf(lrs_ofp, ")"); - -#ifdef MP - fprintf(lrs_ofp, " max digits=%ld/%ld", DIG2DEC(lrs_record_digits), DIG2DEC(lrs_digits)); -#endif - -#ifdef TIMES - ptimes(); -#endif - - fprintf(lrs_ofp, "\n"); -#endif // LRS_LOGGING - // fclose (lrs_ifp); - // if (lrs_ofp != stdout) - // fclose (lrs_ofp); -} - -/***********************************/ -/* allocate and initialize lrs_dat */ -/***********************************/ -lrs_dat *lrs_alloc_dat(const char *name) -{ - lrs_dat *Q; - long i; - - if (lrs_global_count >= MAX_LRS_GLOBALS) { - fprintf(stderr, "Fatal: Attempt to allocate more than %ld global data blocks\n", - MAX_LRS_GLOBALS); - exit(1); - } - - Q = (lrs_dat *)malloc(sizeof(lrs_dat)); - if (Q == NULL) { - return Q; /* failure to allocate */ - } - - lrs_global_list[lrs_global_count] = Q; - Q->id = lrs_global_count; - lrs_global_count++; - Q->name = (char *)CALLOC((unsigned)strlen(name) + 1, sizeof(char)); - strcpy(Q->name, name); - - /* initialize variables */ - Q->m = 0L; - Q->n = 0L; - Q->inputd = 0L; - Q->deepest = 0L; - Q->nlinearity = 0L; - Q->nredundcol = 0L; - Q->runs = 0L; - Q->subtreesize = MAXD; - Q->seed = 1234L; - Q->totalnodes = 0L; - for (i = 0; i < 10; i++) { - Q->count[i] = 0L; - Q->cest[i] = 0.0; - if (i < 5) { - Q->startcount[i] = 0L; - } - } - Q->count[2] = 1L; /* basis counter */ - Q->startcount[2] = 0L; /* starting basis counter */ - /* initialize flags */ - Q->allbases = FALSE; - Q->bound = FALSE; /* upper/lower bound on objective function given */ - Q->countonly = FALSE; /* produce the usual output */ - Q->debug = FALSE; - Q->frequency = 0L; - Q->dualdeg = FALSE; /* TRUE if dual degenerate starting dictionary */ - Q->geometric = FALSE; - Q->getvolume = FALSE; - Q->homogeneous = TRUE; - Q->polytope = FALSE; - Q->hull = FALSE; - Q->incidence = FALSE; - Q->lponly = FALSE; - Q->maxdepth = MAXD; - Q->mindepth = -MAXD; - Q->maxoutput = 0L; - Q->maxcobases = 0L; /* after maxcobases have been found unexplored subtrees reported */ - Q->nash = FALSE; - Q->nonnegative = FALSE; - Q->printcobasis = FALSE; - Q->printslack = FALSE; - Q->truncate = FALSE; /* truncate tree when moving from opt vertex */ - Q->verbose = FALSE; - Q->voronoi = FALSE; - Q->maximize = FALSE; /*flag for LP maximization */ - Q->minimize = FALSE; /*flag for LP minimization */ - Q->restart = FALSE; /* TRUE if restarting from some cobasis */ - Q->givenstart = FALSE; /* TRUE if a starting cobasis is given */ - Q->strace = -1L; /* turn on debug at basis # strace */ - Q->etrace = -1L; /* turn off debug at basis # etrace */ - - Q->saved_flag = 0; /* no cobasis saved initially, db */ - lrs_alloc_mp(Q->Nvolume); - lrs_alloc_mp(Q->Dvolume); - lrs_alloc_mp(Q->sumdet); - lrs_alloc_mp(Q->saved_det); - lrs_alloc_mp(Q->boundn); - lrs_alloc_mp(Q->boundd); - itomp(ZERO, Q->Nvolume); - itomp(ONE, Q->Dvolume); - itomp(ZERO, Q->sumdet); - /* 2012.6.1 */ - Q->unbounded = FALSE; - - return Q; -} /* end of allocate and initialize lrs_dat */ - -/*******************************/ -/* lrs_read_dat */ -/*******************************/ -long lrs_read_dat(lrs_dat *Q, int argc, char *argv[]) -{ - char name[100]; - long dec_digits = 0; - long infile = 0; /*input file number to open if any */ - long firstline = TRUE; /*flag for picking off name at line 1 */ - - int c; /* for fgetc */ - - if (argc > 1) { - infile = 1; - } - if (Q->nash && argc == 2) { /* open second nash input file */ - infile = 2; - } - - if (infile > 0) /* command line argument overides stdin */ - { - if ((lrs_ifp = fopen(argv[infile], "r")) == NULL) { - printf("\nBad input file name\n"); - return (FALSE); - } - else { - if (infile == 1) { - printf("\n*Input taken from file %s", argv[infile]); - } - } - } - - /* command line argument overides stdout */ - if ((!Q->nash && argc == 3) || (Q->nash && argc == 4)) { - if ((lrs_ofp = fopen(argv[argc - 1], "w")) == NULL) { - printf("\nBad output file name\n"); - return (FALSE); - } - else { - printf("\n*Output sent to file %s\n", argv[argc - 1]); - } - } - - /* process input file */ - if (fscanf(lrs_ifp, "%s", name) == EOF) { - fprintf(lrs_ofp, "\nNo begin line"); - return (FALSE); - } - - while (strcmp(name, "begin") != 0) /*skip until "begin" found processing options */ - { - if (strncmp(name, "*", 1) == 0) /* skip any line beginning with * */ - { - c = name[0]; - while (c != EOF && c != '\n') { - c = fgetc(lrs_ifp); - } - } - - else if (strcmp(name, "H-representation") == 0) { - Q->hull = FALSE; - } - else if ((strcmp(name, "hull") == 0) || (strcmp(name, "V-representation") == 0)) { - Q->hull = TRUE; - Q->polytope = TRUE; /* will be updated as input read */ - } - else if (strcmp(name, "digits") == 0) { - if (fscanf(lrs_ifp, "%ld", &dec_digits) == EOF) { - fprintf(lrs_ofp, "\nNo begin line"); - return (FALSE); - } - if (!lrs_set_digits(dec_digits)) { - return (FALSE); - } - } - else if (strcmp(name, "linearity") == 0) { - if (!readlinearity(Q)) { - return FALSE; - } - } - else if (strcmp(name, "nonnegative") == 0) { - if (Q->nash) { - fprintf(lrs_ofp, "\nNash incompatibile with nonnegative option - skipped"); - } - else { - Q->nonnegative = TRUE; - } - } - else if (firstline) { - stringcpy(Q->fname, name); - fprintf(lrs_ofp, "\n%s", Q->fname); - firstline = FALSE; - } - - if (fscanf(lrs_ifp, "%s", name) == EOF) { - fprintf(lrs_ofp, "\nNo begin line"); - return (FALSE); - } - - } /* end of while */ - - if (fscanf(lrs_ifp, "%ld %ld %s", &Q->m, &Q->n, name) == EOF) { - fprintf(lrs_ofp, "\nNo data in file"); - return (FALSE); - } - if (strcmp(name, "integer") != 0 && strcmp(name, "rational") != 0) { - fprintf(lrs_ofp, "\nData type must be integer of rational"); - return (FALSE); - } - - if (Q->m == 0) { - fprintf(lrs_ofp, "\nNo input given"); /* program dies ungracefully */ - return (FALSE); - } - - /* inputd may be reduced in preprocessing of linearities and redund cols */ - - return TRUE; -} /* end of lrs_read_dat */ - -/****************************/ -/* set up lrs_dic structure */ -/****************************/ -long lrs_read_dic(lrs_dic *P, lrs_dat *Q) -/* read constraint matrix and set up problem and dictionary */ - -{ - lrs_mp Temp, Tempn, Tempd, mpone, mpten; - lrs_mp_vector oD; /* Denom for objective function */ - - long i, j; - char name[100]; - int c; /* fgetc actually returns an int. db */ - - /* assign local variables to structures */ - - lrs_mp_matrix A; - lrs_mp_vector Gcd, Lcm; - long hull = Q->hull; - long m, d; - long dualperturb = FALSE; /* dualperturb=TRUE: objective function perturbed */ - - lrs_alloc_mp(Temp); - lrs_alloc_mp(mpone); - lrs_alloc_mp(Tempn); - lrs_alloc_mp(Tempd); - lrs_alloc_mp(mpten); - A = P->A; - m = Q->m; - d = Q->inputd; - Gcd = Q->Gcd; - Lcm = Q->Lcm; - - oD = lrs_alloc_mp_vector(d); - - itomp(ONE, mpone); - itomp(10L, mpten); - itomp(ONE, A[0][0]); - itomp(ONE, Lcm[0]); - itomp(ONE, Gcd[0]); - - for (i = 1; i <= m; i++) /* read in input matrix row by row */ - { - itomp(ONE, Lcm[i]); /* Lcm of denominators */ - itomp(ZERO, Gcd[i]); /* Gcd of numerators */ - for (j = hull; j <= d; j++) /* hull data copied to cols 1..d */ - { - if (readrat(A[i][j], A[0][j])) { - lcm(Lcm[i], A[0][j]); /* update lcm of denominators */ - } - copy(Temp, A[i][j]); - gcd(Gcd[i], Temp); /* update gcd of numerators */ - } - - if (hull) { - itomp(ZERO, A[i][0]); /*for hull, we have to append an extra column of zeroes */ - if (!one(A[i][1]) || !one(A[0][1])) { /* all rows must have a one in column one */ - Q->polytope = FALSE; - } - } - if (!zero(A[i][hull])) { /* for H-rep, are zero in column 0 */ - Q->homogeneous = FALSE; /* for V-rep, all zero in column 1 */ - } - - storesign(Gcd[i], POS); - storesign(Lcm[i], POS); - if (mp_greater(Gcd[i], mpone) || mp_greater(Lcm[i], mpone)) { - for (j = 0; j <= d; j++) { - exactdivint(A[i][j], Gcd[i], Temp); /*reduce numerators by Gcd */ - mulint(Lcm[i], Temp, Temp); /*remove denominators */ - exactdivint(Temp, A[0][j], A[i][j]); /*reduce by former denominator */ - } - } - - } /* end of for i= */ - - /* 2010.4.26 patch */ - if (Q->nonnegative) { /* set up Gcd and Lcm for nonexistent nongative inequalities */ - for (i = m + 1; i <= m + d; i++) { - itomp(ONE, Lcm[i]); - itomp(ONE, Gcd[i]); - } - } - - if (Q->homogeneous && Q->verbose) { - fprintf(lrs_ofp, "\n*Input is homogeneous, column 1 not treated as redundant"); - } - - /* read in flags */ - while (fscanf(lrs_ifp, "%s", name) != EOF) { - if (strncmp(name, "*", 1) == 0) /* skip any line beginning with * */ - { - c = name[0]; - while (c != EOF && c != '\n') { - c = fgetc(lrs_ifp); - } - } - - if (strcmp(name, "checkpoint") == 0) { - long seconds; - - if (fscanf(lrs_ifp, "%ld", &seconds) == EOF) { - fprintf(lrs_ofp, "\nInvalid checkpoint option"); - return (FALSE); - } - -#ifdef SIGNALS - if (seconds > 0) { - lrs_checkpoint_seconds = seconds; - errcheck("signal", signal(SIGALRM, timecheck)); - alarm(lrs_checkpoint_seconds); - } -#endif - } - - if (strcmp(name, "debug") == 0) { - Q->etrace = 0; - if (fscanf(lrs_ifp, "%ld %ld", &Q->strace, &Q->etrace) == EOF) { - Q->strace = 0; - } - fprintf(lrs_ofp, "\n*%s from B#%ld to B#%ld", name, Q->strace, Q->etrace); - Q->verbose = TRUE; - if (Q->strace <= 1) { - Q->debug = TRUE; - } - } - if (strcmp(name, "startingcobasis") == 0) { - if (Q->nonnegative) { - fprintf(lrs_ofp, "\n*startingcobasis incompatible with nonnegative option:skipped"); - } - else { - fprintf(lrs_ofp, "\n*startingcobasis"); - Q->givenstart = TRUE; - if (!readfacets(Q, Q->inequality)) { - return FALSE; - } - } - } - - if (strcmp(name, "restart") == 0) { - Q->restart = TRUE; - if (Q->voronoi) { - if (fscanf(lrs_ifp, "%ld %ld %ld %ld", &Q->count[1], &Q->count[0], &Q->count[2], - &P->depth) == EOF) { - return FALSE; - } - fprintf(lrs_ofp, "\n*%s V#%ld R#%ld B#%ld h=%ld data points", name, Q->count[1], - Q->count[0], Q->count[2], P->depth); - } - else if (hull) { - if (fscanf(lrs_ifp, "%ld %ld %ld", &Q->count[0], &Q->count[2], &P->depth) == EOF) { - fprintf(lrs_ofp, "\n*%s F#%ld B#%ld h=%ld vertices/rays", name, Q->count[0], Q->count[2], - P->depth); - } - } - else { - if (fscanf(lrs_ifp, "%ld %ld %ld %ld", &Q->count[1], &Q->count[0], &Q->count[2], - &P->depth) == EOF) { - return FALSE; - } - fprintf(lrs_ofp, "\n*%s V#%ld R#%ld B#%ld h=%ld facets", name, Q->count[1], Q->count[0], - Q->count[2], P->depth); - } - if (!readfacets(Q, Q->facet)) { - return FALSE; - } - } /* end of restart */ - - /* The next flag request a LP solution only */ - if (strcmp(name, "lponly") == 0) { - if (Q->hull) { - fprintf(lrs_ofp, "\n*lponly option not valid for V-representation-skipped"); - } - else { - Q->lponly = TRUE; - } - } - - /* The LP will be solved after initialization to get starting vertex */ - /* Used also with lponly flag */ - if (strcmp(name, "maximize") == 0 || strcmp(name, "minimize") == 0) { - if (Q->hull) { - fprintf(lrs_ofp, "\n*%s option not valid for V-representation-skipped", name); - } - else { - { - if (strcmp(name, "maximize") == 0) { - Q->maximize = TRUE; - } - else { - Q->minimize = TRUE; - } - } - fprintf(lrs_ofp, "\n*%s", name); - - if (dualperturb) /* apply a perturbation to objective function */ - { - fprintf(lrs_ofp, " - Objective function perturbed"); - copy(Temp, mpten); - for (j = 0; j <= 10; j++) { - mulint(mpten, Temp, Temp); - } - } - - fprintf(lrs_ofp, ": "); - - for (j = 0; j <= d; j++) { - if (readrat(A[0][j], oD[j]) || dualperturb) { - if (dualperturb && j > 0 && j < d) { - if (Q->maximize) { - linrat(A[0][j], oD[j], ONE, mpone, Temp, ONE, Tempn, Tempd); - } - else { - linrat(A[0][j], oD[j], ONE, mpone, Temp, -1L, Tempn, Tempd); - } - - copy(A[0][j], Tempn); - copy(oD[j], Tempd); - mulint(mpten, Temp, Temp); - } - - reduce(A[0][j], oD[j]); - lcm(Q->Lcm[0], oD[j]); /* update lcm of denominators */ - } - prat("", A[0][j], oD[j]); - if (!Q->maximize) { - changesign(A[0][j]); - } - } - storesign(Q->Lcm[0], POS); - if (mp_greater(Q->Lcm[0], mpone)) { - for (j = 0; j <= d; j++) { - mulint(Q->Lcm[0], A[0][j], A[0][j]); /*remove denominators */ - copy(Temp, A[0][j]); - exactdivint(Temp, oD[j], A[0][j]); - } - } - if (Q->debug) { - printA(P, Q); - } - } - } /* end of LP setup */ - if (strcmp(name, "volume") == 0) { - fprintf(lrs_ofp, "\n*%s", name); - Q->getvolume = TRUE; - } - if (strcmp(name, "geometric") == 0) { - fprintf(lrs_ofp, "\n*%s", name); - if (hull & !Q->voronoi) { - fprintf(lrs_ofp, " - option for H-representation or voronoi only, skipped"); - } - else { - Q->geometric = TRUE; - } - } - if (strcmp(name, "allbases") == 0) { - fprintf(lrs_ofp, "\n*%s", name); - Q->allbases = TRUE; - } - - if (strcmp(name, "countonly") == 0) { - fprintf(lrs_ofp, "\n*%s", name); - Q->countonly = TRUE; - } - if (strcmp(name, "dualperturb") == 0) { - dualperturb = TRUE; - } - - if (strcmp(name, "incidence") == 0) { - fprintf(lrs_ofp, "\n*%s", name); - Q->incidence = TRUE; - } - - if (strcmp(name, "#incidence") == 0) /* number of incident inequalities only */ - { - Q->printcobasis = TRUE; - } - - if (strcmp(name, "printcobasis") == 0) { - if (fscanf(lrs_ifp, "%ld", &Q->frequency) == EOF) { - /*2010.7.7 set default to zero = print only when outputting vertex/ray/facet */ - Q->frequency = 0; - } - fprintf(lrs_ofp, "\n*%s", name); - if (Q->frequency > 0) { - fprintf(lrs_ofp, " %ld", Q->frequency); - } - Q->printcobasis = TRUE; - } - - if (strcmp(name, "printslack") == 0) { - Q->printslack = TRUE; - } - - if (strcmp(name, "cache") == 0) { - if (fscanf(lrs_ifp, "%ld", &dict_limit) == EOF) { - dict_limit = 1; - } - fprintf(lrs_ofp, "\n*cache %ld", dict_limit); - if (dict_limit < 1) { - dict_limit = 1; - } - } - if (strcmp(name, "linearity") == 0) { - if (!readlinearity(Q)) { - return FALSE; - } - } - - if (strcmp(name, "maxdepth") == 0) { - if (fscanf(lrs_ifp, "%ld", &Q->maxdepth) == EOF) { - Q->maxdepth = MAXD; - } - fprintf(lrs_ofp, "\n*%s %ld", name, Q->maxdepth); - } - - if (strcmp(name, "maxoutput") == 0) { - if (fscanf(lrs_ifp, "%ld", &Q->maxoutput) == EOF) { - Q->maxoutput = 100; - } - fprintf(lrs_ofp, "\n*%s %ld", name, Q->maxoutput); - } - - if (strcmp(name, "maxcobases") == 0) { - if (fscanf(lrs_ifp, "%ld", &Q->maxcobases) == EOF) { - Q->maxcobases = 1000; - } - fprintf(lrs_ofp, "\n*%s %ld", name, Q->maxcobases); - } - - if (strcmp(name, "mindepth") == 0) { - if (fscanf(lrs_ifp, "%ld", &Q->mindepth) == EOF) { - Q->mindepth = 0; - } - fprintf(lrs_ofp, "\n*%s %ld", name, Q->mindepth); - } - - if (strcmp(name, "truncate") == 0) { - fprintf(lrs_ofp, "\n*%s", name); - if (!hull) { - Q->truncate = TRUE; - } - else { - fprintf(lrs_ofp, " - option for H-representation only, skipped"); - } - } - - if (strcmp(name, "verbose") == 0) { - Q->verbose = TRUE; - } - - if (strcmp(name, "bound") == 0) { - readrat(Q->boundn, Q->boundd); - Q->bound = TRUE; - } - - if (strcmp(name, "nonnegative") == 0) { - fprintf(lrs_ofp, "\n*%s", name); - fprintf(lrs_ofp, " - option must come before begin line - skipped"); - } - - if (strcmp(name, "seed") == 0) { - if (fscanf(lrs_ifp, "%ld", &Q->seed) == EOF) { - Q->seed = 3142; - } - fprintf(lrs_ofp, "\n*seed= %ld ", Q->seed); - } - - if (strcmp(name, "estimates") == 0) { - if (fscanf(lrs_ifp, "%ld", &Q->runs) == EOF) { - Q->runs = 1; - } - fprintf(lrs_ofp, "\n*%ld %s", Q->runs, name); - } - - // 2015.2.9 Estimates will continue until estimate is less than subtree size - if (strcmp(name, "subtreesize") == 0) { - if (fscanf(lrs_ifp, "%ld", &Q->subtreesize) == EOF) { - Q->subtreesize = MAXD; - } - fprintf(lrs_ofp, "\n*%s %ld", name, Q->subtreesize); - } - - if ((strcmp(name, "voronoi") == 0) || (strcmp(name, "Voronoi") == 0)) { - if (!hull) { - fprintf(lrs_ofp, "\n*voronoi requires V-representation - option skipped"); - } - else { - Q->voronoi = TRUE; - Q->polytope = FALSE; - } - } - - } /* end of while for reading flags */ - - if (Q->polytope) { - Q->getvolume = TRUE; /* might as well get volume, it doesn't cost much */ - } - - if (Q->bound && Q->maximize) { - prat("\n*Lower bound on objective function:", Q->boundn, Q->boundd); - } - - if (Q->bound && Q->minimize) { - prat("\n*Upper bound on objective function:", Q->boundn, Q->boundd); - } - - /* Certain options are incompatible, this is fixed here */ - - if (Q->restart) { - Q->getvolume = FALSE; /* otherwise incorrect volume reported */ - } - - if (Q->restart && Q->maxcobases > 0) { // 2015.4.3 adjust for restart - Q->maxcobases = Q->maxcobases + Q->count[2]; - } - - if (Q->incidence) { - Q->printcobasis = TRUE; - /* 2010.5.7 No need to reset this, as it may have been set by printcobasis */ - /* Q->frequency = ZERO; */ - } - - if (Q->debug) { - printA(P, Q); - fprintf(lrs_ofp, "\nexiting lrs_read_dic"); - } - lrs_clear_mp(Temp); - lrs_clear_mp(mpone); - lrs_clear_mp(Tempn); - lrs_clear_mp(Tempd); - lrs_clear_mp(mpten); - lrs_clear_mp_vector(oD, d); - return TRUE; -} - -/* end of if(voronoi) */ - -/* In lrs_getfirstbasis and lrs_getnextbasis we use D instead of P */ -/* since the dictionary P may change, ie. &P in calling routine */ - -#define D (*D_p) - -long lrs_getfirstbasis(lrs_dic **D_p, lrs_dat *Q, lrs_mp_matrix *Lin, long no_output) -/* gets first basis, FALSE if none */ -/* P may get changed if lin. space Lin found */ -/* no_output is TRUE supresses output headers */ -{ - lrs_mp scale, Temp; - - long i, j, k; - - /* assign local variables to structures */ - - lrs_mp_matrix A; - long *B, *C, *Col; - long *inequality; - long *linearity; - long hull = Q->hull; - long m, d, lastdv, nlinearity, nredundcol; - - lrs_alloc_mp(Temp); - lrs_alloc_mp(scale); - - if (Q->lponly) { - no_output = TRUE; - } - m = D->m; - d = D->d; - - lastdv = Q->lastdv; - - nredundcol = 0L; /* will be set after getabasis */ - nlinearity = Q->nlinearity; /* may be reset if new linearity read or in getabasis*/ - linearity = Q->linearity; - - A = D->A; - B = D->B; - C = D->C; - Col = D->Col; - inequality = Q->inequality; - - if (Q->nlinearity > 0 && Q->nonnegative) { - fprintf(lrs_ofp, "\n*linearity and nonnegative options incompatible"); - fprintf(lrs_ofp, " - all linearities are skipped"); - fprintf(lrs_ofp, "\n*add nonnegative constraints explicitly and "); - fprintf(lrs_ofp, " remove nonnegative option"); - } - - if (Q->nlinearity && Q->voronoi) { - fprintf(lrs_ofp, "\n*linearity and Voronoi options set - results unpredictable"); - } - - if (Q->lponly && !Q->maximize && !Q->minimize) { - fprintf(lrs_ofp, "\n*LP has no objective function given - assuming all zero"); - } - - if (Q->runs > 0) /* arrays for estimator */ - { - Q->isave = (long *)CALLOC((unsigned)(m * d), sizeof(long)); - Q->jsave = (long *)CALLOC((unsigned)(m * d), sizeof(long)); - } - /* default is to look for starting cobasis using linearies first, then */ - /* filling in from last rows of input as necessary */ - /* linearity array is assumed sorted here */ - /* note if restart/given start inequality indices already in place */ - /* from nlinearity..d-1 */ - for (i = 0; i < nlinearity; i++) { /* put linearities first in the order */ - inequality[i] = linearity[i]; - } - - k = 0; /* index for linearity array */ - - if (Q->givenstart) { - k = d; - } - else { - k = nlinearity; - } - for (i = m; i >= 1; i--) { - j = 0; - while (j < k && inequality[j] != i) { - j++; /* see if i is in inequality */ - } - if (j == k) { - inequality[k++] = i; - } - } -#ifndef PLRS - if (Q->debug) { - fprintf(lrs_ofp, "\n*Starting cobasis uses input row order"); - for (i = 0; i < m; i++) { - fprintf(lrs_ofp, " %ld", inequality[i]); - } - } -#endif - /* for voronoi convert to h-description using the transform */ - /* a_0 .. a_d-1 -> (a_0^2 + ... a_d-1 ^2)-2a_0x_0-...-2a_d-1x_d-1 + x_d >= 0 */ - /* note constant term is stored in column d, and column d-1 is all ones */ - /* the other coefficients are multiplied by -2 and shifted one to the right */ - if (Q->debug) { - printA(D, Q); - } - if (Q->voronoi) { - Q->hull = FALSE; - hull = FALSE; - for (i = 1; i <= m; i++) { - if (zero(A[i][1])) { - printf("\nWith voronoi option column one must be all one\n"); - return (FALSE); - } - copy(scale, A[i][1]); /*adjust for scaling to integers of rationals */ - itomp(ZERO, A[i][0]); - for (j = 2; j <= d; j++) /* transform each input row */ - { - copy(Temp, A[i][j]); - mulint(A[i][j], Temp, Temp); - linint(A[i][0], ONE, Temp, ONE); - linint(A[i][j - 1], ZERO, A[i][j], -TWO); - mulint(scale, A[i][j - 1], A[i][j - 1]); - } /* end of for (j=1;..) */ - copy(A[i][d], scale); - mulint(scale, A[i][d], A[i][d]); - } /* end of for (i=1;..) */ -#ifndef PLRS - if (Q->debug) { - printA(D, Q); - } -#endif - } /* end of if(voronoi) */ - if (!Q->maximize && !Q->minimize) { - for (j = 0; j <= d; j++) { - itomp(ZERO, A[0][j]); - } - } - - /* Now we pivot to standard form, and then find a primal feasible basis */ - /* Note these steps MUST be done, even if restarting, in order to get */ - /* the same index/inequality correspondance we had for the original prob. */ - /* The inequality array is used to give the insertion order */ - /* and is defaulted to the last d rows when givenstart=FALSE */ - - if (Q->nonnegative) { - /* no need for initial pivots here, labelling already done */ - Q->lastdv = d; - Q->nredundcol = 0; - } - else { - if (!getabasis(D, Q, inequality)) { - return FALSE; - } - /* bug fix 2009.12.2 */ - nlinearity = Q->nlinearity; /*may have been reset if some lins are redundant*/ - } - -#ifndef PLRS - if (Q->debug) { - - fprintf(lrs_ofp, "\nafter getabasis"); - printA(D, Q); - } -#endif - nredundcol = Q->nredundcol; - lastdv = Q->lastdv; - d = D->d; - - /********************************************************************/ - /* now we start printing the output file unless no output requested */ - /********************************************************************/ - - if (!no_output || Q->debug) { - - if (Q->voronoi) { -#ifndef PLRS - fprintf(lrs_ofp, "\n*Voronoi Diagram: Voronoi vertices and rays are output"); -#else - char *type = "header"; - char *data = "*Voronoi Diagram: Voronoi vertices and rays are output"; - // post output in a nonblocking manner (a consumer thread will manage output) - post_output(type, data); -#endif - } - if (hull) { -#ifndef PLRS - fprintf(lrs_ofp, "\nH-representation"); -#else - char *type = "header"; - char *data = "H-representation"; - // post output in a nonblocking manner (a consumer thread will manage output) - post_output(type, data); -#endif - } - else { -#ifndef PLRS - fprintf(lrs_ofp, "\nV-representation"); -#else - char *type = "header"; - char *data = "V-representation"; - // post output in a nonblocking manner (a consumer thread will manage output) - post_output(type, data); -#endif - } - - /* Print linearity space */ - /* Don't print linearity if first column zero in hull computation */ - - if (hull && Q->homogeneous) { - k = 1; /* 0 normally, 1 for homogeneous case */ - } - else { - k = 0; - } - - if (nredundcol > k) { -#ifndef PLRS - fprintf(lrs_ofp, "\nlinearity %ld ", nredundcol - k); /*adjust nredundcol for homog. */ -#else - stringstream ss; - char *type = "header"; - ss << "linearity " << (nredundcol - k); -#endif - for (i = 1; i <= nredundcol - k; i++) { -#ifndef PLRS - fprintf(lrs_ofp, " %ld", i); -#else - ss << " " << i; -#endif - } -#ifdef PLRS - // post output in a nonblocking manner (a consumer thread will manage output) - post_output(type, ss.str().c_str()); -#endif - } /* end print of linearity space */ - -#ifndef PLRS - fprintf(lrs_ofp, "\nbegin"); - fprintf(lrs_ofp, "\n***** %ld rational", Q->n); -#else - char *type = "header"; - stringstream ss; - ss << "begin" << endl << "***** " << Q->n << " rational"; - post_output(type, ss.str().c_str()); -#endif - } - - /* end of if !no_output ....... */ - - /* Reset up the inequality array to remember which index is which input inequality */ - /* inequality[B[i]-lastdv] is row number of the inequality with index B[i] */ - /* inequality[C[i]-lastdv] is row number of the inequality with index C[i] */ - - for (i = 1; i <= m; i++) { - inequality[i] = i; - } - if (nlinearity > 0) /* some cobasic indices will be removed */ - { - for (i = 0; i < nlinearity; i++) { /* remove input linearity indices */ - inequality[linearity[i]] = 0; - } - k = 1; /* counter for linearities */ - for (i = 1; i <= m - nlinearity; i++) { - while (k <= m && inequality[k] == 0) { - k++; /* skip zeroes in corr. to linearity */ - } - inequality[i] = inequality[k++]; - } - } - /* end if linearity */ - -#ifndef PLRS - if (Q->debug) { - fprintf(lrs_ofp, "\ninequality array initialization:"); - for (i = 1; i <= m - nlinearity; i++) { - fprintf(lrs_ofp, " %ld", inequality[i]); - } - } -#endif - - if (nredundcol > 0) { - const unsigned int Qn = Q->n; - *Lin = lrs_alloc_mp_matrix(nredundcol, Qn); - - for (i = 0; i < nredundcol; i++) { - - if (!(Q->homogeneous && Q->hull && i == 0)) /* skip redund col 1 for homog. hull */ - { - - lrs_getray(D, Q, Col[0], D->C[0] + i - hull, (*Lin)[i]); /* adjust index for deletions */ - } - - if (!removecobasicindex(D, Q, 0L)) { - lrs_clear_mp_matrix(*Lin, nredundcol, Qn); - return FALSE; - } - } - - } /* end if nredundcol > 0 */ - - if (Q->lponly || Q->nash) { - if (Q->verbose) { - fprintf(lrs_ofp, "\nNumber of pivots for starting dictionary: %ld", Q->count[3]); - if (Q->lponly) { - printA(D, Q); - } - } - } - - /* Do dual pivots to get primal feasibility */ - if (!primalfeasible(D, Q)) { -#ifndef LRS_QUIET - fprintf(lrs_ofp, "\nNo feasible solution\n"); -#endif - if (Q->nash && Q->verbose) { - fprintf(lrs_ofp, "\nNumber of pivots for feasible solution: %ld", Q->count[3]); - fprintf(lrs_ofp, " - No feasible solution"); - } - return FALSE; - } - - if (Q->lponly || Q->nash) { - if (Q->verbose) { - fprintf(lrs_ofp, "\nNumber of pivots for feasible solution: %ld", Q->count[3]); - if (Q->lponly) { - printA(D, Q); - } - } - } - - /* Now solve LP if objective function was given */ - if (Q->maximize || Q->minimize) { - Q->unbounded = !lrs_solvelp(D, Q, Q->maximize); - if (Q->lponly) { - -#ifndef PLRS - if (Q->verbose) { - fprintf(lrs_ofp, "\nNumber of pivots for optimum solution: %ld", Q->count[3]); - printA(D, Q); - } -#endif - lrs_clear_mp(Temp); - lrs_clear_mp(scale); - return TRUE; - } - - else /* check to see if objective is dual degenerate */ - { - j = 1; - while (j <= d && !zero(A[0][j])) { - j++; - } - if (j <= d) { - Q->dualdeg = TRUE; - } - } - } - else - /* re-initialize cost row to -det */ - { - for (j = 1; j <= d; j++) { - copy(A[0][j], D->det); - storesign(A[0][j], NEG); - } - - itomp(ZERO, A[0][0]); /* zero optimum objective value */ - } - -/* reindex basis to 0..m if necessary */ -/* we use the fact that cobases are sorted by index value */ -#ifndef PLRS - if (Q->debug) { - printA(D, Q); - } -#endif - - while (C[0] <= m) { - i = C[0]; - j = inequality[B[i] - lastdv]; - inequality[B[i] - lastdv] = inequality[C[0] - lastdv]; - inequality[C[0] - lastdv] = j; - C[0] = B[i]; - B[i] = i; - reorder1(C, Col, ZERO, d); - } - -#ifndef PLRS - if (Q->debug) { - fprintf(lrs_ofp, "\n*Inequality numbers for indices %ld .. %ld : ", lastdv + 1, m + d); - for (i = 1; i <= m - nlinearity; i++) { - fprintf(lrs_ofp, " %ld ", inequality[i]); - } - printA(D, Q); - } -#endif - - if (Q->restart) { -#ifndef PLRS - if (Q->debug) { - fprintf(lrs_ofp, "\nPivoting to restart co-basis"); - } -#endif - if (!restartpivots(D, Q)) { - return FALSE; - } - D->lexflag = lexmin(D, Q, ZERO); /* see if lexmin basis */ -#ifndef PLRS - if (Q->debug) { - printA(D, Q); - } -#endif - } - - /* Check to see if necessary to resize */ - if (Q->inputd > d) { - *D_p = resize(D, Q); - } - - lrs_clear_mp(Temp); - lrs_clear_mp(scale); - return TRUE; -} -/********* end of lrs_getfirstbasis ***************/ - -/*****************************************/ -/* getnextbasis in reverse search order */ -/*****************************************/ - -long lrs_getnextbasis(lrs_dic **D_p, lrs_dat *Q, long backtrack) -/* gets next reverse search tree basis, FALSE if none */ -/* switches to estimator if maxdepth set */ -/* backtrack TRUE means backtrack from here */ - -{ - /* assign local variables to structures */ - long i = 0L, j = 0L; - long m = D->m; - long d = D->d; - long saveflag; - long cob_est = 0; /* estimated number of cobases in subtree from current node */ - - if (backtrack && D->depth == 0) { - return FALSE; /* cannot backtrack from root */ - } - - if (Q->maxoutput > 0 && Q->count[0] + Q->count[1] - Q->hull >= Q->maxoutput) { - return FALSE; /* output limit reached */ - } - - while ((j < d) || (D->B[m] != m)) /*main while loop for getnextbasis */ - { - if (D->depth >= Q->maxdepth) { - if (Q->runs > 0 && !backtrack) /*get an estimate of remaining tree */ - { - - // 2015.2.9 do iterative estimation backtracking when estimate is small - - saveflag = Q->printcobasis; - Q->printcobasis = FALSE; - cob_est = lrs_estimate(D, Q); - Q->printcobasis = saveflag; - if (cob_est <= Q->subtreesize) /* stop iterative estimation */ - { - if (cob_est > 0) /* when zero we are at a leaf */ - { - lrs_printcobasis(D, Q, ZERO); -#ifndef PLRS - fprintf(lrs_ofp, " cob_est= %ld *subtree", cob_est); -#else - if (PLRS_DEBUG) { - stringstream ss; - ss << "cob_est= " << cob_est << " *subtree" << endl; - post_output("debug", ss.str().c_str()); - } -#endif - } - backtrack = TRUE; - } - } - else // either not estimating or we are backtracking - - if (!backtrack && !Q->printcobasis) { - if (!lrs_leaf(D, Q)) { /* 2015.6.5 cobasis returned if not a leaf */ - lrs_printcobasis(D, Q, ZERO); - } - } - - backtrack = TRUE; - - if (Q->maxdepth == 0 && cob_est <= Q->subtreesize) { /* root estimate only */ - return FALSE; /* no nextbasis */ - } - } // if (D->depth >= Q->maxdepth) - - /* if ( Q->truncate && negative(D->A[0][0]))*/ /* truncate when moving from opt. vertex */ - /* backtrack = TRUE; 2011.7.14 */ - - if (backtrack) /* go back to prev. dictionary, restore i,j */ - { - backtrack = FALSE; - - if (check_cache(D_p, Q, &i, &j)) { - if (Q->debug) { - fprintf(lrs_ofp, "\n Cached Dict. restored to depth %ld\n", D->depth); - } - } - else { - D->depth--; - selectpivot(D, Q, &i, &j); - pivot(D, Q, i, j); - update(D, Q, &i, &j); /*Update B,C,i,j */ - } - - if (Q->debug) { - fprintf(lrs_ofp, "\n Backtrack Pivot: indices i=%ld j=%ld depth=%ld", i, j, D->depth); - printA(D, Q); - }; - - j++; /* go to next column */ - } /* end of if backtrack */ - - if (D->depth < Q->mindepth) { - break; - } - - /* try to go down tree */ - - /* 2011.7.14 patch */ - while ((j < d) && (!reverse(D, Q, &i, j) || (Q->truncate && Q->minratio[D->m] == 1))) { - j++; - } - if (j == d) { - backtrack = TRUE; - } - else - /*reverse pivot found */ - { - cache_dict(D_p, Q, i, j); - /* Note that the next two lines must come _after_ the - call to cache_dict */ - - D->depth++; - if (D->depth > Q->deepest) { - Q->deepest++; - } - - pivot(D, Q, i, j); - update(D, Q, &i, &j); /*Update B,C,i,j */ - - D->lexflag = lexmin(D, Q, ZERO); /* see if lexmin basis */ - Q->count[2]++; - Q->totalnodes++; - - save_basis(*D_p, Q); - if (Q->strace == Q->count[2]) { - Q->debug = TRUE; - } - if (Q->etrace == Q->count[2]) { - Q->debug = FALSE; - } - return TRUE; /*return new dictionary */ - } - - } /* end of main while loop for getnextbasis */ - return FALSE; /* done, no more bases */ -} /*end of lrs_getnextbasis */ - -/*************************************/ -/* print out one line of output file */ -/*************************************/ -long lrs_getvertex(lrs_dic *P, lrs_dat *Q, lrs_mp_vector output) -/*Print out current vertex if it is lexmin and return it in output */ -/* return FALSE if no output generated */ -{ - lrs_mp_matrix A = P->A; - - long i; - long ind; /* output index */ - long ired; /* counts number of redundant columns */ - /* assign local variables to structures */ - long *redundcol = Q->redundcol; - long *count = Q->count; - long *B = P->B; - long *Row = P->Row; - - long lastdv = Q->lastdv; - - long hull; - long lexflag; - - hull = Q->hull; - lexflag = P->lexflag; - if (lexflag || Q->allbases) { - ++(Q->count[1]); - } -#ifdef PLRS - // do not print vertex again in PLRS at root - if (P->depth == Q->mindepth) { - return FALSE; - } - -#else - // If we are at minimum depth and not at root do not print vertex - if (P->depth == Q->mindepth && Q->mindepth != 0) { - return FALSE; - } -#endif - - if (Q->debug) { - printA(P, Q); - } - - linint(Q->sumdet, 1, P->det, 1); - if (Q->getvolume) { - updatevolume(P, Q); - if (Q->verbose) { /* this will print out a triangulation */ - lrs_printcobasis(P, Q, ZERO); - } - } - - /*print cobasis if printcobasis=TRUE and count[2] a multiple of frequency */ - /* or for lexmin basis, except origin for hull computation - ugly! */ - - if (Q->printcobasis) { - if ((lexflag && !hull) || - ((Q->frequency > 0) && (count[2] == (count[2] / Q->frequency) * Q->frequency))) { - if (P->depth != Q->mindepth || - Q->mindepth == 0) { // Don't print cobasis if this is a restart cobasis - lrs_printcobasis(P, Q, ZERO); - } - } - } - - if (hull) { - return FALSE; /* skip printing the origin */ - } - - if (!lexflag && !Q->allbases && !Q->lponly) { /* not lexmin, and not printing forced */ - return FALSE; - } - - /* copy column 0 to output */ - - i = 1; - ired = 0; - copy(output[0], P->det); - - for (ind = 1; ind < Q->n; ind++) { /* extract solution */ - - if ((ired < Q->nredundcol) && (redundcol[ired] == ind)) - /* column was deleted as redundant */ - { - itomp(ZERO, output[ind]); - ired++; - } - else - /* column not deleted as redundant */ - { - getnextoutput(P, Q, i, ZERO, output[ind]); - i++; - } - } - - reducearray(output, Q->n); - if (lexflag && one(output[0])) { - ++Q->count[4]; /* integer vertex */ - } - - /* uncomment to print nonzero basic variables - - printf("\n nonzero basis: vars"); - for(i=1;i<=lastdv; i++) - { - if ( !zero(A[Row[i]][0]) ) - printf(" %ld ",B[i]); - } - */ - - /* printslack inequality indices */ - - if (Q->printslack) { - fprintf(lrs_ofp, "\nslack ineq:"); - for (i = lastdv + 1; i <= P->m; i++) { - if (!zero(A[Row[i]][0])) { - fprintf(lrs_ofp, " %ld ", Q->inequality[B[i] - lastdv]); - } - } - } - - return TRUE; -} /* end of lrs_getvertex */ - -long lrs_getray(lrs_dic *P, lrs_dat *Q, long col, long redcol, lrs_mp_vector output) -/*Print out solution in col and return it in output */ -/*redcol =n for ray/facet 0..n-1 for linearity column */ -/*hull=1 implies facets will be recovered */ -/* return FALSE if no output generated in column col */ -{ - long i; - long ind; /* output index */ - long ired; /* counts number of redundant columns */ - /* assign local variables to structures */ - long *redundcol = Q->redundcol; - long *count = Q->count; - long hull = Q->hull; - long n = Q->n; - - long *B = P->B; - long *Row = P->Row; - long lastdv = Q->lastdv; - -#ifdef PLRS - // do not print vertex again in PLRS at root - if (P->depth == Q->mindepth) { - return FALSE; - } - -#else - // If we are at minimum depth and not at origin do not print ray - if (P->depth == Q->mindepth && Q->mindepth != 0) { - return FALSE; - } -#endif - - if (Q->debug) { - printA(P, Q); - for (i = 0; i < Q->nredundcol; i++) { - fprintf(lrs_ofp, " %ld", redundcol[i]); - } - fflush(lrs_ofp); - } - - if (redcol == n) { - ++count[0]; - if (Q->printcobasis) { - if (P->depth != Q->mindepth || - Q->mindepth == 0) { // Don't print cobasis if this is a restart cobasis - lrs_printcobasis(P, Q, col); - } - } - } - - i = 1; - ired = 0; - - for (ind = 0; ind < n; ind++) /* print solution */ - { - if (ind == 0 && !hull) { /* must have a ray, set first column to zero */ - itomp(ZERO, output[0]); - } - - else if ((ired < Q->nredundcol) && (redundcol[ired] == ind)) - /* column was deleted as redundant */ - { - if (redcol == ind) { /* true for linearity on this cobasic index */ - /* we print reduced determinant instead of zero */ - copy(output[ind], P->det); - } - else { - itomp(ZERO, output[ind]); - } - ired++; - } - else - /* column not deleted as redundant */ - { - getnextoutput(P, Q, i, col, output[ind]); - i++; - } - } - reducearray(output, n); - /* printslack for rays: 2006.10.10 */ - /* printslack inequality indices */ - - if (Q->printslack) { - fprintf(lrs_ofp, "\nslack ineq:"); - for (i = lastdv + 1; i <= P->m; i++) { - if (!zero(P->A[Row[i]][col])) { - fprintf(lrs_ofp, " %ld ", Q->inequality[B[i] - lastdv]); - } - } - } - - return TRUE; -} /* end of lrs_getray */ - -void getnextoutput(lrs_dic *P, lrs_dat *Q, long i, long col, lrs_mp out) -/* get A[B[i][col] and copy to out */ -{ - long row; - long m = P->m; - long d = P->d; - long lastdv = Q->lastdv; - lrs_mp_matrix A = P->A; - long *B = P->B; - long *Row = P->Row; - long j; - - if (i == d && Q->voronoi) { - return; /* skip last column if voronoi set */ - } - - row = Row[i]; - - if (Q->nonnegative) /* if m+i basic get correct value from dictionary */ - /* the slack for the inequality m-d+i contains decision */ - /* variable x_i. We first see if this is in the basis */ - /* otherwise the value of x_i is zero, except for a ray */ - /* when it is one (det/det) for the actual column it is in */ - { - for (j = lastdv + 1; j <= m; j++) { - if (Q->inequality[B[j] - lastdv] == m - d + i) { - copy(out, A[Row[j]][col]); - return; - } - } - /* did not find inequality m-d+i in basis */ - if (i == col) { - copy(out, P->det); - } - else { - itomp(ZERO, out); - } - } - else { - copy(out, A[row][col]); - } - -} /* end of getnextoutput */ - -void lrs_printcobasis(lrs_dic *P, lrs_dat *Q, long col) -/* col is output column being printed */ -{ - -#ifdef PLRS - long i; - long rflag; /* used to find inequality number for ray column */ - /* assign local variables to structures */ - lrs_mp_matrix A = P->A; - lrs_mp Nvol, Dvol; /* hold rescaled det of current basis */ - long *B = P->B; - long *C = P->C; - long *Col = P->Col; - long *Row = P->Row; - long *inequality = Q->inequality; - long *temparray = Q->temparray; - long *count = Q->count; - long hull = Q->hull; - long d = P->d; - long lastdv = Q->lastdv; - long m = P->m; - long firstime = TRUE; - long nincidence; /* count number of tight inequalities */ - - // Make new output node - char *type = "cobasis"; - // Make stream to collect prat / pmp data - stringstream ss; - - lrs_alloc_mp(Nvol); - lrs_alloc_mp(Dvol); - - if (hull) { - ss << "F#" << count[0] << " B#" << count[2] << " h=" << P->depth << " vertices/rays "; - } - else if (Q->voronoi) { - ss << "V#" << count[1] << " R#" << count[0] << " B#" << count[2] << " h=" << P->depth - << " data points "; - } - else { - ss << "V#" << count[1] << " R#" << count[0] << " B#" << count[2] << " h=" << P->depth - << " facets "; - } - - rflag = (-1); - for (i = 0; i < d; i++) { - temparray[i] = inequality[C[i] - lastdv]; - if (Col[i] == col) { - rflag = temparray[i]; /* look for ray index */ - } - } - for (i = 0; i < d; i++) { - reorder(temparray, d); - } - for (i = 0; i < d; i++) { - ss << " " << temparray[i]; - - /* missing cobasis element for ray */ - if (!(col == ZERO) && (rflag == temparray[i])) { - ss << "*"; - type = "V cobasis"; - } - } - - /* get and print incidence information */ - if (col == 0) { - nincidence = d; - } - else { - nincidence = d - 1; - } - - for (i = lastdv + 1; i <= m; i++) { - if (zero(A[Row[i]][0])) { - if ((col == ZERO) || zero(A[Row[i]][col])) { - nincidence++; - if (Q->incidence) { - if (firstime) { - ss << " :"; - firstime = FALSE; - } - ss << inequality[B[i] - lastdv]; - } - } - } - } - - ss << " I#" << nincidence; - - ss << pmp(" det=", P->det); - // fflush (lrs_ofp); - rescaledet(P, Q, Nvol, Dvol); /* scales determinant in case input rational */ - - ss << prat(" in_det=", Nvol, Dvol); - - // pipe stream into output node - // post output in a nonblocking manner (a consumer thread will manage output) - post_output(type, ss.str().c_str()); - - lrs_clear_mp(Nvol); - lrs_clear_mp(Dvol); - -#else - long i; - long rflag; /* used to find inequality number for ray column */ - /* assign local variables to structures */ - lrs_mp_matrix A = P->A; - lrs_mp Nvol, Dvol; /* hold rescaled det of current basis */ - long *B = P->B; - long *C = P->C; - long *Col = P->Col; - long *Row = P->Row; - long *inequality = Q->inequality; - long *temparray = Q->temparray; - long *count = Q->count; - long hull = Q->hull; - long d = P->d; - long lastdv = Q->lastdv; - long m = P->m; - long firstime = TRUE; - long nincidence; /* count number of tight inequalities */ - - lrs_alloc_mp(Nvol); - lrs_alloc_mp(Dvol); - - if (hull) { - fprintf(lrs_ofp, "\nF#%ld B#%ld h=%ld vertices/rays ", count[0], count[2], P->depth); - } - else if (Q->voronoi) { - fprintf(lrs_ofp, "\nV#%ld R#%ld B#%ld h=%ld data points ", count[1], count[0], count[2], - P->depth); - } - else { - fprintf(lrs_ofp, "\nV#%ld R#%ld B#%ld h=%ld facets ", count[1], count[0], count[2], P->depth); - } - - rflag = (-1); - for (i = 0; i < d; i++) { - temparray[i] = inequality[C[i] - lastdv]; - if (Col[i] == col) { - rflag = temparray[i]; /* look for ray index */ - } - } - for (i = 0; i < d; i++) { - reorder(temparray, d); - } - for (i = 0; i < d; i++) { - fprintf(lrs_ofp, " %ld", temparray[i]); - - if (!(col == ZERO) && (rflag == temparray[i])) { /* missing cobasis element for ray */ - fprintf(lrs_ofp, "*"); - } - } - - /* get and print incidence information */ - if (col == 0) { - nincidence = d; - } - else { - nincidence = d - 1; - } - - for (i = lastdv + 1; i <= m; i++) { - if (zero(A[Row[i]][0])) { - if ((col == ZERO) || zero(A[Row[i]][col])) { - nincidence++; - if (Q->incidence) { - if (firstime) { - fprintf(lrs_ofp, " :"); - firstime = FALSE; - } - fprintf(lrs_ofp, " %ld", inequality[B[i] - lastdv]); - } - } - } - } - - fprintf(lrs_ofp, " I#%ld", nincidence); - - pmp(" det=", P->det); - fflush(lrs_ofp); - rescaledet(P, Q, Nvol, Dvol); /* scales determinant in case input rational */ - prat(" in_det=", Nvol, Dvol); - prat(" z=", P->objnum, P->objden); - lrs_clear_mp(Nvol); - lrs_clear_mp(Dvol); -#endif - -} /* end of lrs_printcobasis */ - -/*********************/ -/* print final totals */ -/*********************/ -void lrs_printtotals(lrs_dic *P, lrs_dat *Q) -{ - -#ifdef PLRS - - long *count = Q->count; - long *startcount = Q->startcount; - std::stringstream ss; - - // output node number of basis - ss.str(""); - ss << count[2] - startcount[2]; - post_output("basis count", ss.str().c_str()); - - if (Q->hull) { - // output node for number of facets - ss.str(""); - ss << count[0] - startcount[0]; - post_output("facet count", ss.str().c_str()); - - rescalevolume(P, Q, Q->Nvolume, Q->Dvolume); - - ss.str(""); - string str1 = prat("", Q->Nvolume, Q->Dvolume); -// strip trailing blank introduced by prat -// for some reason next line fails for mp library ! 2014.12.3 so no volume is reported! -#if (defined(LRSLONG) || defined(GMP)) - ss << str1.substr(0, str1.length() - 1); -#endif - post_output("volume", ss.str().c_str()); - } - else { - // output node for number of vertices - ss.str(""); - ss << count[1] - startcount[1]; - post_output("vertex count", ss.str().c_str()); - - // output node for number of rays - ss.str(""); - ss << count[0] - startcount[0]; - post_output("ray count", ss.str().c_str()); - - // output node for number of integer vertices - /* inaccurate for plrs as restart command does not contain number of integer vertices : an - * overcount is produced */ - - ss.str(""); - ss << count[4] - startcount[4]; - post_output("integer vertex count", ss.str().c_str()); - } - -#else - long i; - double x; - /* local assignments */ - double *cest = Q->cest; - long *count = Q->count; - long *inequality = Q->inequality; - long *linearity = Q->linearity; - long *temparray = Q->temparray; - - long *C = P->C; - - long hull = Q->hull; - long homogeneous = Q->homogeneous; - long nlinearity = Q->nlinearity; - long nredundcol = Q->nredundcol; - long d, lastdv; - d = P->d; - lastdv = Q->lastdv; - - fprintf(lrs_ofp, "\nend"); - if (Q->dualdeg) { - fprintf(lrs_ofp, "\n*Warning: Starting dictionary is dual degenerate"); - fprintf(lrs_ofp, "\n*Complete enumeration may not have been produced"); - if (Q->maximize) { - fprintf(lrs_ofp, - "\n*Recommendation: Add dualperturb option before maximize in input file\n"); - } - else { - fprintf(lrs_ofp, - "\n*Recommendation: Add dualperturb option before minimize in input file\n"); - } - } - - if (Q->unbounded) { - fprintf(lrs_ofp, "\n*Warning: Starting dictionary contains rays"); - fprintf(lrs_ofp, "\n*Complete enumeration may not have been produced"); - if (Q->maximize) { - fprintf(lrs_ofp, "\n*Recommendation: Change or remove maximize option or add bounds\n"); - } - else { - fprintf(lrs_ofp, "\n*Recommendation: Change or remove minimize option or add bounds\n"); - } - } - if (Q->truncate) { - fprintf(lrs_ofp, "\n*Tree truncated at each new vertex"); - } - if (Q->maxdepth < MAXD) { - fprintf(lrs_ofp, "\n*Tree truncated at depth %ld", Q->maxdepth); - } - if (Q->maxoutput > 0L) { - fprintf(lrs_ofp, "\n*Maximum number of output lines = %ld", Q->maxoutput); - } - -#ifdef LRSLONG - fprintf(lrs_ofp, "\n*Caution: no overflow checking with long integer arithemtic"); -#else - if (Q->verbose) { - fprintf(lrs_ofp, "\n*Sum of det(B)="); - pmp("", Q->sumdet); - } -#endif - - /* next block with volume rescaling must come before estimates are printed */ - - if (Q->getvolume) { - rescalevolume(P, Q, Q->Nvolume, Q->Dvolume); - - if (Q->polytope) { - prat("\n*Volume=", Q->Nvolume, Q->Dvolume); - } - else { - prat("\n*Pseudovolume=", Q->Nvolume, Q->Dvolume); - } - } - - if (hull) /* output things that are specific to hull computation */ - { - fprintf(lrs_ofp, "\n*Totals: facets=%ld bases=%ld", count[0], count[2]); - - if (nredundcol > homogeneous) /* don't count column 1 as redundant if homogeneous */ - { - fprintf(lrs_ofp, " linearities=%ld", nredundcol - homogeneous); - fprintf(lrs_ofp, " facets+linearities=%ld", nredundcol - homogeneous + count[0]); - } - if (lrs_ofp != stdout) { - printf("\n*Totals: facets=%ld bases=%ld", count[0], count[2]); - - if (nredundcol > homogeneous) /* don't count column 1 as redundant if homogeneous */ - { - printf(" linearities=%ld", nredundcol - homogeneous); - printf(" facets+linearities=%ld", nredundcol - homogeneous + count[0]); - } - } - - if ((cest[2] > 0) || (cest[0] > 0)) { - fprintf(lrs_ofp, "\n*Estimates: facets=%.0f bases=%.0f", count[0] + cest[0], - count[2] + cest[2]); - if (Q->getvolume) { - rattodouble(Q->Nvolume, Q->Dvolume, &x); - for (i = 2; i < d; i++) { - cest[3] = cest[3] / i; /*adjust for dimension */ - } - fprintf(lrs_ofp, " volume=%g", cest[3] + x); - } - - fprintf(lrs_ofp, "\n*Total number of tree nodes evaluated: %ld", Q->totalnodes); -#ifdef TIMES - fprintf(lrs_ofp, "\n*Estimated total running time=%.1f secs ", - (count[2] + cest[2]) / Q->totalnodes * get_time()); -#endif - } - /* Should not happen since we homogenize */ - /* - if ( Q-> restart || Q->allbases || (count[0] > 1 && !Q->homogeneous && !Q->polytope)) - fprintf (lrs_ofp, "\n*Note! Duplicate facets may be present"); - */ - } - else /* output things specific to vertex/ray computation */ - { - fprintf(lrs_ofp, "\n*Totals: vertices=%ld rays=%ld bases=%ld", count[1], count[0], count[2]); - - fprintf(lrs_ofp, " integer_vertices=%ld ", count[4]); - - if (nredundcol > 0) { - fprintf(lrs_ofp, " linearities=%ld", nredundcol); - } - if (count[0] + nredundcol > 0) { - fprintf(lrs_ofp, " vertices+rays"); - if (nredundcol > 0) { - fprintf(lrs_ofp, "+linearities"); - } - fprintf(lrs_ofp, "=%ld", nredundcol + count[0] + count[1]); - } - - if (lrs_ofp != stdout) { - printf("\n*Totals: vertices=%ld rays=%ld bases=%ld", count[1], count[0], count[2]); - - printf(" integer_vertices=%ld ", count[4]); - - if (nredundcol > 0) { - printf(" linearities=%ld", nredundcol); - } - if (count[0] + nredundcol > 0) { - printf(" vertices+rays"); - if (nredundcol > 0) { - printf("+linearities"); - } - printf("=%ld", nredundcol + count[0] + count[1]); - } - } /* end lrs_ofp != stdout */ - - if ((cest[2] > 0) || (cest[0] > 0)) { - fprintf(lrs_ofp, "\n*Estimates: vertices=%.0f rays=%.0f", count[1] + cest[1], - count[0] + cest[0]); - fprintf(lrs_ofp, " bases=%.0f integer_vertices=%.0f ", count[2] + cest[2], - count[4] + cest[4]); - - if (Q->getvolume) { - rattodouble(Q->Nvolume, Q->Dvolume, &x); - for (i = 2; i <= d - homogeneous; i++) { - cest[3] = cest[3] / i; /*adjust for dimension */ - } - fprintf(lrs_ofp, " pseudovolume=%g", cest[3] + x); - } - fprintf(lrs_ofp, "\n*Total number of tree nodes evaluated: %ld", Q->totalnodes); -#ifdef TIMES - fprintf(lrs_ofp, "\n*Estimated total running time=%.1f secs ", - (count[2] + cest[2]) / Q->totalnodes * get_time()); -#endif - } - - if (Q->restart || Q->allbases) { /* print warning */ - fprintf(lrs_ofp, "\n*Note! Duplicate vertices/rays may be present"); - } - - else if ((count[0] > 1 && !Q->homogeneous)) { - fprintf(lrs_ofp, "\n*Note! Duplicate rays may be present"); - } - - } /* end of output for vertices/rays */ - - fprintf(lrs_ofp, "\n*Dictionary Cache: max size= %ld misses= %ld/%ld Tree Depth= %ld", - dict_count, cache_misses, cache_tries, Q->deepest); - if (lrs_ofp != stdout) { - printf("\n*Dictionary Cache: max size= %ld misses= %ld/%ld Tree Depth= %ld", dict_count, - cache_misses, cache_tries, Q->deepest); - } - - if (!Q->verbose) { - return; - } - - fprintf(lrs_ofp, "\n*Input size m=%ld rows n=%ld columns", P->m, Q->n); - if (hull) { - fprintf(lrs_ofp, " working dimension=%ld", d - 1 + homogeneous); - } - else { - fprintf(lrs_ofp, " working dimension=%ld", d); - } - - fprintf(lrs_ofp, "\n*Starting cobasis defined by input rows"); - for (i = 0; i < nlinearity; i++) { - temparray[i] = linearity[i]; - } - for (i = nlinearity; i < lastdv; i++) { - temparray[i] = inequality[C[i - nlinearity] - lastdv]; - } - for (i = 0; i < lastdv; i++) { - reorder(temparray, lastdv); - } - for (i = 0; i < lastdv; i++) { - fprintf(lrs_ofp, " %ld", temparray[i]); - } - -#endif - -} /* end of lrs_printtotals */ -/************************/ -/* Estimation function */ -/************************/ -long lrs_estimate(lrs_dic *P, lrs_dat *Q) -/*returns estimate of subtree size (no. cobases) from current node */ -/*current node is not counted. */ -/*cest[0]rays [1]vertices [2]bases [3]volume */ -/* [4] integer vertices */ -{ - - lrs_mp_vector output; /* holds one line of output; ray,vertex,facet,linearity */ - lrs_mp Nvol, Dvol; /* hold volume of current basis */ - long estdepth = 0; /* depth of basis/vertex in subtree for estimate */ - long i = 0, j = 0, k, nchild, runcount, col; - double prod = 0.0; - double cave[] = {0.0, 0.0, 0.0, 0.0, 0.0}; - double nvertices, nbases, nrays, nvol, nivertices; - long rays = 0; - double newvol = 0.0; - /* assign local variables to structures */ - lrs_mp_matrix A = P->A; - long *isave = Q->isave; - long *jsave = Q->jsave; - double *cest = Q->cest; - long d = P->d; - lrs_alloc_mp(Nvol); - lrs_alloc_mp(Dvol); - /* Main Loop of Estimator */ - - output = lrs_alloc_mp_vector(Q->n); /* output holds one line of output from dictionary */ - - for (runcount = 1; runcount <= Q->runs; - runcount++) { /* runcount counts number of random probes */ - j = 0; - nchild = 1; - prod = 1; - nvertices = 0.0; - nbases = 0.0; - nrays = 0.0; - nvol = 0.0; - nivertices = 0.0; - - while (nchild != 0) /* while not finished yet */ - { - - nchild = 0; - while (j < d) { - if (reverse(P, Q, &i, j)) { - isave[nchild] = i; - jsave[nchild] = j; - nchild++; - } - j++; - } - - if (estdepth == 0 && nchild == 0) { - cest[0] = cest[0] + rays; /* may be some rays here */ - lrs_clear_mp(Nvol); - lrs_clear_mp(Dvol); - lrs_clear_mp_vector(output, Q->n); - return (0L); /*subtree is a leaf */ - } - - prod = prod * nchild; - nbases = nbases + prod; - if (Q->debug) { - fprintf(lrs_ofp, " degree= %ld ", nchild); - fprintf(lrs_ofp, "\nPossible reverse pivots: i,j="); - for (k = 0; k < nchild; k++) { - fprintf(lrs_ofp, "%ld,%ld ", isave[k], jsave[k]); - } - } - - if (nchild > 0) /*reverse pivot found choose random child */ - { - k = myrandom(Q->seed, nchild); - Q->seed = myrandom(Q->seed, 977L); - i = isave[k]; - j = jsave[k]; - if (Q->debug) { - fprintf(lrs_ofp, " selected pivot k=%ld seed=%ld ", k, Q->seed); - } - estdepth++; - Q->totalnodes++; /* calculate total number of nodes evaluated */ - pivot(P, Q, i, j); - update(P, Q, &i, &j); /*Update B,C,i,j */ - if (lexmin(P, Q, ZERO)) /* see if lexmin basis for vertex */ - { - nvertices = nvertices + prod; - /* integer vertex estimate */ - if (lrs_getvertex(P, Q, output)) { - --Q->count[1]; - if (one(output[0])) { - --Q->count[4]; - nivertices = nivertices + prod; - } - } - } - - rays = 0; - for (col = 1; col <= d; col++) { - if (negative(A[0][col]) && (lrs_ratio(P, Q, col) == 0) && lexmin(P, Q, col)) { - rays++; - } - } - nrays = nrays + prod * rays; /* update ray info */ - - if (Q->getvolume) { - rescaledet(P, Q, Nvol, Dvol); /* scales determinant in case input rational */ - rattodouble(Nvol, Dvol, &newvol); - nvol = nvol + newvol * prod; /* adjusts volume for degree */ - } - j = 0; - } - } - cave[0] = cave[0] + nrays; - cave[1] = cave[1] + nvertices; - cave[2] = cave[2] + nbases; - cave[3] = cave[3] + nvol; - cave[4] = cave[4] + nivertices; - - /* backtrack to root and do it again */ - - while (estdepth > 0) { - estdepth = estdepth - 1; - selectpivot(P, Q, &i, &j); - pivot(P, Q, i, j); - update(P, Q, &i, &j); /*Update B,C,i,j */ - /*fprintf(lrs_ofp,"\n0 +++"); */ - if (Q->debug) { - fprintf(lrs_ofp, "\n Backtrack Pivot: indices i,j %ld %ld ", i, j); - printA(P, Q); - } - j++; - } - - } /* end of for loop on runcount */ - - j = (long)cave[2] / Q->runs; - - // 2015.2.9 Do not update totals if we do iterative estimating and subtree is too big - if (Q->subtreesize == 0 || j <= Q->subtreesize) { - for (i = 0; i < 5; i++) { - cest[i] = cave[i] / Q->runs + cest[i]; - } - } - - lrs_clear_mp(Nvol); - lrs_clear_mp(Dvol); - lrs_clear_mp_vector(output, Q->n); - return (j); -} /* end of lrs_estimate */ - -/*********************************/ -/* Internal functions */ -/*********************************/ -/* Basic Dictionary functions */ -/******************************* */ - -long reverse(lrs_dic *P, lrs_dat *Q, long *r, long s) -/* find reverse indices */ -/* TRUE if B[*r] C[s] is a reverse lexicographic pivot */ -{ - long i, j, enter, row, col; - - /* assign local variables to structures */ - lrs_mp_matrix A = P->A; - long *B = P->B; - long *C = P->C; - long *Row = P->Row; - long *Col = P->Col; - long d = P->d; - - enter = C[s]; - col = Col[s]; - if (Q->debug) { - fprintf(lrs_ofp, "\n+reverse: col index %ld C %ld Col %ld ", s, enter, col); - fflush(lrs_ofp); - } - if (!negative(A[0][col])) { - if (Q->debug) { - fprintf(lrs_ofp, " Pos/Zero Cost Coeff"); - } - Q->minratio[P->m] = 0; /* 2011.7.14 */ - return (FALSE); - } - - *r = lrs_ratio(P, Q, col); - if (*r == 0) /* we have a ray */ - { - if (Q->debug) { - fprintf(lrs_ofp, " Pivot col non-negative: ray found"); - } - Q->minratio[P->m] = 0; /* 2011.7.14 */ - return (FALSE); - } - - row = Row[*r]; - - /* check cost row after "pivot" for smaller leaving index */ - /* ie. j s.t. A[0][j]*A[row][col] < A[0][col]*A[row][j] */ - /* note both A[row][col] and A[0][col] are negative */ - - for (i = 0; i < d && C[i] < B[*r]; i++) { - if (i != s) { - j = Col[i]; - if (positive(A[0][j]) || negative(A[row][j])) { /*or else sign test fails trivially */ - if ((!negative(A[0][j]) && !positive(A[row][j])) || - comprod(A[0][j], A[row][col], A[0][col], A[row][j]) == -1) { /*+ve cost found */ - if (Q->debug) { - fprintf(lrs_ofp, "\nPositive cost found: index %ld C %ld Col %ld", i, C[i], j); - fflush(lrs_ofp); - } - Q->minratio[P->m] = 0; /* 2011.7.14 */ - - return (FALSE); - } - } - } - } - if (Q->debug) { - fprintf(lrs_ofp, "\n+end of reverse : indices r %ld s %ld \n", *r, s); - fflush(stdout); - } - return (TRUE); -} /* end of reverse */ - -long selectpivot(lrs_dic *P, lrs_dat *Q, long *r, long *s) -/* select pivot indices using lexicographic rule */ -/* returns TRUE if pivot found else FALSE */ -/* pivot variables are B[*r] C[*s] in locations Row[*r] Col[*s] */ -{ - long j, col; - /* assign local variables to structures */ - lrs_mp_matrix A = P->A; - long *Col = P->Col; - long d = P->d; - - *r = 0; - *s = d; - j = 0; - - /*find positive cost coef */ - while ((j < d) && (!positive(A[0][Col[j]]))) { - j++; - } - - if (j < d) /* pivot column found! */ - { - *s = j; - col = Col[j]; - - /*find min index ratio */ - *r = lrs_ratio(P, Q, col); - if (*r != 0) { - return (TRUE); /* unbounded */ - } - } - return (FALSE); -} /* end of selectpivot */ -/******************************************************* */ - -void pivot(lrs_dic *P, lrs_dat *Q, long bas, long cob) -/* Qpivot routine for array A */ -/* indices bas, cob are for Basis B and CoBasis C */ -/* corresponding to row Row[bas] and column */ -/* Col[cob] respectively */ -{ - long r, s; - long i, j; - lrs_mp Ns, Nt, Ars; - /* assign local variables to structures */ - - lrs_mp_matrix A = P->A; - long *B = P->B; - long *C = P->C; - long *Row = P->Row; - long *Col = P->Col; - long d, m_A; - - lrs_alloc_mp(Ns); - lrs_alloc_mp(Nt); - lrs_alloc_mp(Ars); - - d = P->d; - m_A = P->m_A; - Q->count[3]++; /* count the pivot */ - - r = Row[bas]; - s = Col[cob]; - - /* Ars=A[r][s] */ - if (Q->debug) { - fprintf(lrs_ofp, "\n pivot B[%ld]=%ld C[%ld]=%ld ", bas, B[bas], cob, C[cob]); - printA(P, Q); - fflush(stdout); - } - copy(Ars, A[r][s]); - storesign(P->det, sign(Ars)); /*adjust determinant to new sign */ - - for (i = 0; i <= m_A; i++) { - if (i != r) { - for (j = 0; j <= d; j++) { - if (j != s) { - /* A[i][j]=(A[i][j]*Ars-A[i][s]*A[r][j])/P->det; */ - - mulint(A[i][j], Ars, Nt); - mulint(A[i][s], A[r][j], Ns); - decint(Nt, Ns); - exactdivint(Nt, P->det, A[i][j]); - } /* end if j .... */ - } - } - } - - if (sign(Ars) == POS) { - for (j = 0; j <= d; j++) { /* no need to change sign if Ars neg */ - /* A[r][j]=-A[r][j]; */ - if (!zero(A[r][j])) { - changesign(A[r][j]); - } - } - } /* watch out for above "if" when removing this "}" ! */ - else { - for (i = 0; i <= m_A; i++) { - if (!zero(A[i][s])) { - changesign(A[i][s]); - } - } - } - - /* A[r][s]=P->det; */ - - copy(A[r][s], P->det); /* restore old determinant */ - copy(P->det, Ars); - storesign(P->det, POS); /* always keep positive determinant */ - - if (Q->debug) { - fprintf(lrs_ofp, " depth=%ld ", P->depth); - pmp("det=", P->det); - fflush(stdout); - } - /* set the new rescaled objective function value */ - - mulint(P->det, Q->Lcm[0], P->objden); - mulint(Q->Gcd[0], A[0][0], P->objnum); - - if (!Q->maximize) { - changesign(P->objnum); - } - if (zero(P->objnum)) { - storesign(P->objnum, POS); - } - else { - reduce(P->objnum, P->objden); - } - - lrs_clear_mp(Ns); - lrs_clear_mp(Nt); - lrs_clear_mp(Ars); -} /* end of pivot */ - -long primalfeasible(lrs_dic *P, lrs_dat *Q) -/* Do dual pivots to get primal feasibility */ -/* Note that cost row is all zero, so no ratio test needed for Dual Bland's rule */ -{ - long primalinfeasible = TRUE; - long i, j; - /* assign local variables to structures */ - lrs_mp_matrix A = P->A; - long *Row = P->Row; - long *Col = P->Col; - long m, d, lastdv; - m = P->m; - d = P->d; - lastdv = Q->lastdv; - - /*temporary: try to get new start after linearity */ - - while (primalinfeasible) { - i = lastdv + 1; - while (i <= m && !negative(A[Row[i]][0])) { - i++; - } - if (i <= m) { - j = 0; /*find a positive entry for in row */ - while (j < d && !positive(A[Row[i]][Col[j]])) { - j++; - } - if (j >= d) { - return (FALSE); /* no positive entry */ - } - pivot(P, Q, i, j); - update(P, Q, &i, &j); - } - else { - primalinfeasible = FALSE; - } - } /* end of while primalinfeasibile */ - return (TRUE); -} /* end of primalfeasible */ - -long lrs_solvelp(lrs_dic *P, lrs_dat *Q, long maximize) -/* Solve primal feasible lp by Dantzig`s rule and lexicographic ratio test */ -/* return TRUE if bounded, FALSE if unbounded */ -{ - long i, j; - /* assign local variables to structures */ - long d = P->d; - - while (dan_selectpivot(P, Q, &i, &j)) { - Q->count[3]++; - pivot(P, Q, i, j); - update(P, Q, &i, &j); /*Update B,C,i,j */ - } - if (Q->debug) { - printA(P, Q); - } - - if (j < d && i == 0) /* selectpivot gives information on unbounded solution */ - { -#ifndef LRS_QUIET - if (Q->lponly) { - fprintf(lrs_ofp, "\n*Unbounded solution"); - } -#endif - return FALSE; - } - return TRUE; -} /* end of lrs_solvelp */ - -long getabasis(lrs_dic *P, lrs_dat *Q, long order[]) - -/* Pivot Ax<=b to standard form */ -/*Try to find a starting basis by pivoting in the variables x[1]..x[d] */ -/*If there are any input linearities, these appear first in order[] */ -/* Steps: (a) Try to pivot out basic variables using order */ -/* Stop if some linearity cannot be made to leave basis */ -/* (b) Permanently remove the cobasic indices of linearities */ -/* (c) If some decision variable cobasic, it is a linearity, */ -/* and will be removed. */ - -{ - long i, j, k; - /* assign local variables to structures */ - lrs_mp_matrix A = P->A; - long *B = P->B; - long *C = P->C; - long *Row = P->Row; - long *Col = P->Col; - long *linearity = Q->linearity; - long *redundcol = Q->redundcol; - long m, d, nlinearity; - long nredundcol = 0L; /* will be calculated here */ - m = P->m; - d = P->d; - nlinearity = Q->nlinearity; - - if (Q->debug) { - fprintf(lrs_ofp, "\ngetabasis from inequalities given in order"); - for (i = 0l; i < m; i++) { - fprintf(lrs_ofp, " %ld", order[i]); - } - } - for (j = 0l; j < m; j++) { - i = 0l; - while (i <= m && B[i] != d + order[j]) { - i++; /* find leaving basis index i */ - } - if (j < nlinearity && i > m) /* cannot pivot linearity to cobasis */ - { - if (Q->debug) { - printA(P, Q); - } -#ifndef LRS_QUIET - fprintf(lrs_ofp, "\nCannot find linearity in the basis"); -#endif - return FALSE; - } - if (i <= m) { /* try to do a pivot */ - k = 0l; - while (C[k] <= d && zero(A[Row[i]][Col[k]])) { - k++; - } - if (C[k] <= d) { - - pivot(P, Q, i, k); - update(P, Q, &i, &k); - } - else if (j < nlinearity) { /* cannot pivot linearity to cobasis */ - if (zero(A[Row[i]][0])) { -#ifndef LRS_QUIET - fprintf(lrs_ofp, "\n*Input linearity in row %ld is redundant--converted to inequality", - order[j]); -#endif - linearity[j] = 0l; - } - else { - if (Q->debug) { - printA(P, Q); - } -#ifndef LRS_QUIET - fprintf(lrs_ofp, - "\n*Input linearity in row %ld is inconsistent with earlier linearities", - order[j]); - fprintf(lrs_ofp, "\n*No feasible solution"); -#endif - return FALSE; - } - } - } - } - - /* update linearity array to get rid of redundancies */ - i = 0; - k = 0; /* counters for linearities */ - while (k < nlinearity) { - while (k < nlinearity && linearity[k] == 0) { - k++; - } - if (k < nlinearity) { - linearity[i++] = linearity[k++]; - } - } - - nlinearity = i; - /* bug fix, 2009.6.27 */ Q->nlinearity = i; - - /* column dependencies now can be recorded */ - /* redundcol contains input column number 0..n-1 where redundancy is */ - k = 0; - while (k < d && C[k] <= d) { - if (C[k] <= d) { /* decision variable still in cobasis */ - redundcol[nredundcol++] = C[k] - Q->hull; /* adjust for hull indices */ - } - k++; - } - - /* now we know how many decision variables remain in problem */ - Q->nredundcol = nredundcol; - Q->lastdv = d - nredundcol; - if (Q->debug) { - fprintf(lrs_ofp, "\nend of first phase of getabasis: "); - fprintf(lrs_ofp, "lastdv=%ld nredundcol=%ld", Q->lastdv, Q->nredundcol); - fprintf(lrs_ofp, "\nredundant cobases:"); - for (i = 0; i < nredundcol; i++) { - fprintf(lrs_ofp, " %ld", redundcol[i]); - } - printA(P, Q); - } - - /* Remove linearities from cobasis for rest of computation */ - /* This is done in order so indexing is not screwed up */ - - for (i = 0; i < nlinearity; i++) { /* find cobasic index */ - k = 0; - while (k < d && C[k] != linearity[i] + d) { - k++; - } - if (k >= d) { - fprintf(lrs_ofp, "\nError removing linearity"); - return FALSE; - } - if (!removecobasicindex(P, Q, k)) { - return FALSE; - } - d = P->d; - } - if (Q->debug && nlinearity > 0) { - printA(P, Q); - } - /* set index value for first slack variable */ - - /* Check feasability */ - if (Q->givenstart) { - i = Q->lastdv + 1; - while (i <= m && !negative(A[Row[i]][0])) { - i++; - } - if (i <= m) { - fprintf(lrs_ofp, "\n*Infeasible startingcobasis - will be modified"); - } - } - return TRUE; -} /* end of getabasis */ - -long removecobasicindex(lrs_dic *P, lrs_dat *Q, long k) -/* remove the variable C[k] from the problem */ -/* used after detecting column dependency */ -{ - long i, j, cindex, deloc; - /* assign local variables to structures */ - lrs_mp_matrix A = P->A; - long *B = P->B; - long *C = P->C; - long *Col = P->Col; - long m, d; - m = P->m; - d = P->d; - - if (Q->debug) { - fprintf(lrs_ofp, "\nremoving cobasic index k=%ld C[k]=%ld", k, C[k]); - } - cindex = C[k]; /* cobasic index to remove */ - deloc = Col[k]; /* matrix column location to remove */ - - for (i = 1; i <= m; i++) { /* reduce basic indices by 1 after index */ - if (B[i] > cindex) { - B[i]--; - } - } - - for (j = k; j < d; j++) /* move down other cobasic variables */ - { - C[j] = C[j + 1] - 1; /* cobasic index reduced by 1 */ - Col[j] = Col[j + 1]; - } - - if (deloc != d) { - /* copy col d to deloc */ - for (i = 0; i <= m; i++) { - copy(A[i][deloc], A[i][d]); - } - - /* reassign location for moved column */ - j = 0; - while (Col[j] != d) { - j++; - } - - Col[j] = deloc; - } - - P->d--; - if (Q->debug) { - printA(P, Q); - } - return TRUE; -} /* end of removecobasicindex */ - -lrs_dic *resize(lrs_dic *P, lrs_dat *Q) -/* resize the dictionary after some columns are deleted, ie. inputd>d */ -/* a new lrs_dic record is created with reduced size, and items copied over */ -{ - lrs_dic *P1; /* to hold new dictionary in case of resizing */ - - long i, j; - long m, d, m_A; - - m = P->m; - d = P->d; - m_A = P->m_A; - - /* get new dictionary record */ - - P1 = new_lrs_dic(m, d, m_A); - - /* copy data from P to P1 */ - P1->i = P->i; - P1->j = P->j; - P1->depth = P->depth; - P1->m = P->m; - P1->d = P1->d_orig = d; - P1->lexflag = P->lexflag; - P1->m_A = P->m_A; - copy(P1->det, P->det); - copy(P1->objnum, P->objnum); - copy(P1->objden, P->objden); - - for (i = 0; i <= m; i++) { - P1->B[i] = P->B[i]; - P1->Row[i] = P->Row[i]; - } - for (i = 0; i <= m_A; i++) { - for (j = 0; j <= d; j++) { - copy(P1->A[i][j], P->A[i][j]); - } - } - - for (j = 0; j <= d; j++) { - P1->Col[j] = P->Col[j]; - P1->C[j] = P->C[j]; - } - - if (Q->debug) { - fprintf(lrs_ofp, "\nDictionary resized from d=%ld to d=%ld", Q->inputd, P->d); - printA(P1, Q); - } - - lrs_free_dic(P, Q); - - /* Reassign cache pointers */ - - Q->Qhead = P1; - Q->Qtail = P1; - P1->next = P1; - P1->prev = P1; - - return P1; -} -/********* resize ***************/ - -long restartpivots(lrs_dic *P, lrs_dat *Q) -/* facet contains a list of the inequalities in the cobasis for the restart */ -/* inequality contains the relabelled inequalities after initialization */ -{ - long i, j, k; - long *Cobasic; /* when restarting, Cobasic[j]=1 if j is in cobasis */ - /* assign local variables to structures */ - lrs_mp_matrix A = P->A; - long *B = P->B; - long *C = P->C; - long *Row = P->Row; - long *Col = P->Col; - long *inequality = Q->inequality; - long *facet = Q->facet; - long nlinearity = Q->nlinearity; - long m, d, lastdv; - m = P->m; - d = P->d; - lastdv = Q->lastdv; - - Cobasic = (long *)CALLOC((unsigned)m + d + 2, sizeof(long)); - - if (Q->debug) { - fprintf(lrs_ofp, "\nCobasic flags in restartpivots"); - } - /* set Cobasic flags */ - for (i = 0; i < m + d + 1; i++) { - Cobasic[i] = 0; - } - for (i = 0; i < d; i++) /* find index corresponding to facet[i] */ - { - j = 1; - while (facet[i + nlinearity] != inequality[j]) { - j++; - } - Cobasic[j + lastdv] = 1; - if (Q->debug) { - fprintf(lrs_ofp, " %ld %ld;", facet[i + nlinearity], j + lastdv); - } - } - - /* Note that the order of doing the pivots is important, as */ - /* the B and C vectors are reordered after each pivot */ - - /* code below replaced 2006.10.30 */ - /* - - for (i = m; i >= d + 1; i--) - if (Cobasic[B[i]]) - { - k = d - 1; - while ((k >= 0) && - (zero (A[Row[i]][Col[k]]) || Cobasic[C[k]])) - k--; - if (k >= 0) - { - pivot (P, Q, i, k); - update (P, Q, &i, &k); - } - else - { - fprintf (lrs_ofp, "\nInvalid Co-basis - does not have correct rank"); - free(Cobasic); - return FALSE; - } - } - */ - /*end of code that was replaced */ - - /* Suggested new code from db starts */ - i = m; - while (i > d) { - while (Cobasic[B[i]]) { - k = d - 1; - while ((k >= 0) && (zero(A[Row[i]][Col[k]]) || Cobasic[C[k]])) { - k--; - } - if (k >= 0) { - /*db asks: should i really be modified here? (see old code) */ - /*da replies: modifying i only makes is larger, and so */ - /*the second while loop will put it back where it was */ - /*faster (and safer) as done below */ - long ii = i; - pivot(P, Q, ii, k); - update(P, Q, &ii, &k); - } - else { - fprintf(lrs_ofp, "\nInvalid Co-basis - does not have correct rank"); - free(Cobasic); - return FALSE; - } - } - i--; - } - /* Suggested new code from db ends */ - - if (lexmin(P, Q, ZERO)) { - --Q->count[1]; /* decrement vertex count if lexmin */ - } - /* check restarting from a primal feasible dictionary */ - for (i = lastdv + 1; i <= m; i++) { - if (negative(A[Row[i]][0])) { - fprintf(lrs_ofp, "\nTrying to restart from infeasible dictionary"); - free(Cobasic); - return FALSE; - } - } - free(Cobasic); - return TRUE; - -} /* end of restartpivots */ - -long lrs_ratio(lrs_dic *P, lrs_dat *Q, long col) /*find lex min. ratio */ - /* find min index ratio -aig/ais, ais<0 */ - /* if multiple, checks successive basis columns */ - /* recoded Dec 1997 */ -{ - long i, j, comp, ratiocol, basicindex, start, nstart, cindex, bindex; - long firstime; /*For ratio test, true on first pass,else false */ - lrs_mp Nmin, Dmin; - long degencount, ndegencount; - /* assign local variables to structures */ - lrs_mp_matrix A = P->A; - long *B = P->B; - long *Row = P->Row; - long *Col = P->Col; - long *minratio = Q->minratio; - long m, d, lastdv; - - m = P->m; - d = P->d; - lastdv = Q->lastdv; - - nstart = 0; - ndegencount = 0; - degencount = 0; - minratio[P->m] = 1; /*2011.7.14 non-degenerate pivot flag */ - - for (j = lastdv + 1; j <= m; j++) { - /* search rows with negative coefficient in dictionary */ - /* minratio contains indices of min ratio cols */ - if (negative(A[Row[j]][col])) { - minratio[degencount++] = j; - if (zero(A[Row[j]][0])) { - minratio[P->m] = 0; /*2011.7.14 degenerate pivot flag */ - } - } - } /* end of for loop */ - if (Q->debug) { - fprintf(lrs_ofp, " Min ratios: "); - for (i = 0; i < degencount; i++) { - fprintf(lrs_ofp, " %ld ", B[minratio[i]]); - } - } - if (degencount == 0) { - return (degencount); /* non-negative pivot column */ - } - - lrs_alloc_mp(Nmin); - lrs_alloc_mp(Dmin); - ratiocol = 0; /* column being checked, initially rhs */ - start = 0; /* starting location in minratio array */ - bindex = d + 1; /* index of next basic variable to consider */ - cindex = 0; /* index of next cobasic variable to consider */ - basicindex = d; /* index of basis inverse for current ratio test, except d=rhs test */ - while (degencount > 1) /*keep going until unique min ratio found */ - { - if (B[bindex] == basicindex) /* identity col in basis inverse */ - { - if (minratio[start] == bindex) - /* remove this index, all others stay */ - { - start++; - degencount--; - } - bindex++; - } - else - /* perform ratio test on rhs or column of basis inverse */ - { - firstime = TRUE; - /*get next ratio column and increment cindex */ - if (basicindex != d) { - ratiocol = Col[cindex++]; - } - for (j = start; j < start + degencount; j++) { - i = Row[minratio[j]]; /* i is the row location of the next basic variable */ - comp = 1; /* 1: lhs>rhs; 0:lhs=rhs; -1: lhsdebug) { - fprintf(lrs_ofp, " ratiocol=%ld degencount=%ld ", ratiocol, degencount); - fprintf(lrs_ofp, " Min ratios: "); - for (i = start; i < start + degencount; i++) { - fprintf(lrs_ofp, " %ld ", B[minratio[i]]); - } - } - } /*end of while loop */ - lrs_clear_mp(Nmin); - lrs_clear_mp(Dmin); - return (minratio[start]); -} /* end of ratio */ - -long lexmin(lrs_dic *P, lrs_dat *Q, long col) -/*test if basis is lex-min for vertex or ray, if so TRUE */ -/* FALSE if a_r,g=0, a_rs !=0, r > s */ -{ - /*do lexmin test for vertex if col=0, otherwise for ray */ - long r, s, i, j; - /* assign local variables to structures */ - lrs_mp_matrix A = P->A; - long *B = P->B; - long *C = P->C; - long *Row = P->Row; - long *Col = P->Col; - long m = P->m; - long d = P->d; - - for (i = Q->lastdv + 1; i <= m; i++) { - r = Row[i]; - if (zero(A[r][col])) { /* necessary for lexmin to fail */ - for (j = 0; j < d; j++) { - s = Col[j]; - if (B[i] > C[j]) /* possible pivot to reduce basis */ - { - if (zero(A[r][0])) /* no need for ratio test, any pivot feasible */ - { - if (!zero(A[r][s])) { - return (FALSE); - } - } - else if (negative(A[r][s]) && ismin(P, Q, r, s)) { - return (FALSE); - } - } /* end of if B[i] ... */ - } - } - } - if ((col != ZERO) && Q->debug) { - fprintf(lrs_ofp, "\n lexmin ray in col=%ld ", col); - printA(P, Q); - } - return (TRUE); -} /* end of lexmin */ - -long ismin(lrs_dic *P, lrs_dat *Q, long r, long s) -/*test if A[r][s] is a min ratio for col s */ -{ - long i; - /* assign local variables to structures */ - lrs_mp_matrix A = P->A; - long m_A = P->m_A; - - for (i = 1; i <= m_A; i++) { - if ((i != r) && negative(A[i][s]) && comprod(A[i][0], A[r][s], A[i][s], A[r][0])) { - return (FALSE); - } - } - - return (TRUE); -} - -void update(lrs_dic *P, lrs_dat *Q, long *i, long *j) -/*update the B,C arrays after a pivot */ -/* involving B[bas] and C[cob] */ -{ - - long leave, enter; - /* assign local variables to structures */ - long *B = P->B; - long *C = P->C; - long *Row = P->Row; - long *Col = P->Col; - long m = P->m; - long d = P->d; - - leave = B[*i]; - enter = C[*j]; - B[*i] = enter; - reorder1(B, Row, *i, m + 1); - C[*j] = leave; - reorder1(C, Col, *j, d); - /* restore i and j to new positions in basis */ - for (*i = 1; B[*i] != enter; (*i)++) - ; /*Find basis index */ - for (*j = 0; C[*j] != leave; (*j)++) - ; /*Find co-basis index */ -} /* end of update */ - -long lrs_degenerate(lrs_dic *P, lrs_dat *Q) -/* TRUE if the current dictionary is primal degenerate */ -/* not thoroughly tested 2000/02/15 */ -{ - long i; - long *Row; - - lrs_mp_matrix A = P->A; - long d = P->d; - long m = P->m; - - Row = P->Row; - - for (i = d + 1; i <= m; i++) { - if (zero(A[Row[i]][0])) { - return TRUE; - } - } - - return FALSE; -} - -/*********************************************************/ -/* Miscellaneous */ -/******************************************************* */ - -void reorder(long a[], long range) -/*reorder array in increasing order with one misplaced element */ -{ - long i, temp; - for (i = 0; i < range - 1; i++) { - if (a[i] > a[i + 1]) { - temp = a[i]; - a[i] = a[i + 1]; - a[i + 1] = temp; - } - } - for (i = range - 2; i >= 0; i--) { - if (a[i] > a[i + 1]) { - temp = a[i]; - a[i] = a[i + 1]; - a[i + 1] = temp; - } - } - -} /* end of reorder */ - -void reorder1(long a[], long b[], long newone, long range) -/*reorder array a in increasing order with one misplaced element at index newone */ -/*elements of array b are updated to stay aligned with a */ -{ - long temp; - while (newone > 0 && a[newone] < a[newone - 1]) { - temp = a[newone]; - a[newone] = a[newone - 1]; - a[newone - 1] = temp; - temp = b[newone]; - b[newone] = b[newone - 1]; - b[--newone] = temp; - } - while (newone < range - 1 && a[newone] > a[newone + 1]) { - temp = a[newone]; - a[newone] = a[newone + 1]; - a[newone + 1] = temp; - temp = b[newone]; - b[newone] = b[newone + 1]; - b[++newone] = temp; - } -} /* end of reorder1 */ - -void rescaledet(lrs_dic *P, lrs_dat *Q, lrs_mp Vnum, lrs_mp Vden) -/* rescale determinant to get its volume */ -/* Vnum/Vden is volume of current basis */ -{ - lrs_mp gcdprod; /* to hold scale factors */ - long i; - /* assign local variables to structures */ - long *B = P->B; - long *C = P->C; - long m, d, lastdv; - - lrs_alloc_mp(gcdprod); - m = P->m; - d = P->d; - lastdv = Q->lastdv; - - itomp(ONE, gcdprod); - itomp(ONE, Vden); - for (i = 0; i < d; i++) { - if (B[i] <= m) { - mulint(Q->Gcd[Q->inequality[C[i] - lastdv]], gcdprod, gcdprod); - mulint(Q->Lcm[Q->inequality[C[i] - lastdv]], Vden, Vden); - } - } - mulint(P->det, gcdprod, Vnum); - reduce(Vnum, Vden); - lrs_clear_mp(gcdprod); -} /* end rescaledet */ - -void rescalevolume(lrs_dic *P, lrs_dat *Q, lrs_mp Vnum, lrs_mp Vden) -/* adjust volume for dimension */ -{ - lrs_mp temp, dfactorial; - /* assign local variables to structures */ - long lastdv = Q->lastdv; - - lrs_alloc_mp(temp); - lrs_alloc_mp(dfactorial); - - /*reduce Vnum by d factorial */ - getfactorial(dfactorial, lastdv); - mulint(dfactorial, Vden, Vden); - if (Q->hull && !Q->homogeneous) { /* For hull option multiply by d to correct for lifting */ - itomp(lastdv, temp); - mulint(temp, Vnum, Vnum); - } - - reduce(Vnum, Vden); - lrs_clear_mp(temp); - lrs_clear_mp(dfactorial); -} - -void updatevolume(lrs_dic *P, lrs_dat *Q) /* rescale determinant and update the volume */ -{ - lrs_mp tN, tD, Vnum, Vden; - lrs_alloc_mp(tN); - lrs_alloc_mp(tD); - lrs_alloc_mp(Vnum); - lrs_alloc_mp(Vden); - rescaledet(P, Q, Vnum, Vden); - copy(tN, Q->Nvolume); - copy(tD, Q->Dvolume); - linrat(tN, tD, ONE, Vnum, Vden, ONE, Q->Nvolume, Q->Dvolume); - if (Q->debug) { - prat("\n*Volume=", Q->Nvolume, Q->Dvolume); - pmp(" Vnum=", Vnum); - pmp(" Vden=", Vden); - } - lrs_clear_mp(tN); - lrs_clear_mp(tD); - lrs_clear_mp(Vnum); - lrs_clear_mp(Vden); - -} /* end of updatevolume */ - -/***************************************************/ -/* Routines for redundancy checking */ -/***************************************************/ - -long checkredund(lrs_dic *P, lrs_dat *Q) -/* Solve primal feasible lp by least subscript and lex min basis method */ -/* to check redundancy of a row in objective function */ -/* returns TRUE if redundant, else FALSE */ -{ - lrs_mp Ns, Nt; - long i, j; - long r, s; - - /* assign local variables to structures */ - lrs_mp_matrix A = P->A; - long *Row, *Col; - long d = P->d; - - lrs_alloc_mp(Ns); - lrs_alloc_mp(Nt); - Row = P->Row; - Col = P->Col; - - while (selectpivot(P, Q, &i, &j)) { - Q->count[2]++; - - /* sign of new value of A[0][0] */ - /* is A[0][s]*A[r][0]-A[0][0]*A[r][s] */ - - r = Row[i]; - s = Col[j]; - - mulint(A[0][s], A[r][0], Ns); - mulint(A[0][0], A[r][s], Nt); - - if (mp_greater(Ns, Nt)) { - lrs_clear_mp(Ns); - lrs_clear_mp(Nt); - return FALSE; /* non-redundant */ - } - - pivot(P, Q, i, j); - update(P, Q, &i, &j); /*Update B,C,i,j */ - } - - lrs_clear_mp(Ns); - lrs_clear_mp(Nt); - - return !(j < d && i == 0); /* unbounded is also non-redundant */ - -} /* end of checkredund */ - -long checkcobasic(lrs_dic *P, lrs_dat *Q, long index) -/* TRUE if index is cobasic and nonredundant */ -/* FALSE if basic, or degen. cobasic, where it will get pivoted out */ - -{ - - /* assign local variables to structures */ - - lrs_mp_matrix A = P->A; - long *B, *C, *Row, *Col; - long d = P->d; - long m = P->m; - long debug = Q->debug; - long i = 0; - long j = 0; - long s; - - B = P->B; - C = P->C; - Row = P->Row; - Col = P->Col; - - while ((j < d) && C[j] != index) { - j++; - } - - if (j == d) { - return FALSE; /* not cobasic index */ - } - - /* index is cobasic */ - - if (debug) { - fprintf(lrs_ofp, "\nindex=%ld cobasic", index); - } - /* not debugged for new LOC - s=LOC[index]; - */ - s = Col[j]; - i = Q->lastdv + 1; - - while ((i <= m) && (zero(A[Row[i]][s]) || !zero(A[Row[i]][0]))) { - i++; - } - - if (i > m) { - if (debug) { - fprintf(lrs_ofp, " is non-redundant"); - } - return TRUE; - } - if (debug) { - fprintf(lrs_ofp, " is degenerate B[i]=%ld", B[i]); - } - - pivot(P, Q, i, j); - update(P, Q, &i, &j); /*Update B,C,i,j */ - - return FALSE; /*index is no longer cobasic */ - -} /* end of checkcobasic */ - -long checkindex(lrs_dic *P, lrs_dat *Q, long index) -/* 0 if index is non-redundant inequality */ -/* 1 if index is redundant inequality */ -/* 2 if index is input linearity */ -/*NOTE: row is returned all zero if redundant!! */ -{ - long i, j; - - lrs_mp_matrix A = P->A; - long *Row = P->Row; - long *B = P->B; - long d = P->d; - long m = P->m; - - if (Q->debug) { - printA(P, Q); - } - - /* each slack index must be checked for redundancy */ - /* if in cobasis, it is pivoted out if degenerate */ - /* else it is non-redundant */ - - if (checkcobasic(P, Q, index)) { - return ZERO; - } - - /* index is basic */ - /* not debugged for new LOC - i=LOC[index]; - */ - j = 1; - while ((j <= m) && (B[j] != index)) { - j++; - } - - i = Row[j]; - - /* copy row i to cost row, and set it to zero */ - - for (j = 0; j <= d; j++) { - copy(A[0][j], A[i][j]); - changesign(A[0][j]); - itomp(ZERO, A[i][j]); - } - - if (checkredund(P, Q)) { - return ONE; - } - - /* non-redundant, copy back and change sign */ - - for (j = 0; j <= d; j++) { - copy(A[i][j], A[0][j]); - changesign(A[i][j]); - } - - return ZERO; - -} /* end of checkindex */ - -/***************************************************************/ -/* */ -/* Package of I/O routines */ -/* */ -/***************************************************************/ - -void lprat(const char *name, long Nt, long Dt) -/*print the long precision rational Nt/Dt without reducing */ -{ - if (Nt > 0) { - fprintf(lrs_ofp, " "); - } - fprintf(lrs_ofp, "%s%ld", name, Nt); - if (Dt != 1) { - fprintf(lrs_ofp, "/%ld", Dt); - } - fprintf(lrs_ofp, " "); -} /* lprat */ - -long lreadrat(long *Num, long *Den) -/* read a rational string and convert to long */ -/* returns true if denominator is not one */ -{ - char in[MAXINPUT], num[MAXINPUT], den[MAXINPUT]; - if (fscanf(lrs_ifp, "%s", in) == EOF) { - return (FALSE); - } - atoaa(in, num, den); /*convert rational to num/dem strings */ - *Num = atol(num); - if (den[0] == '\0') { - *Den = 1L; - return (FALSE); - } - *Den = atol(den); - return (TRUE); -} - -void lrs_getinput(lrs_dic *P, lrs_dat *Q, long *num, long *den, long m, long d) -/* code for reading data matrix in lrs/cdd format */ -{ - long j, row; - - printf("\nEnter each row: b_i a_ij j=1..%ld", d); - for (row = 1; row <= m; row++) { - printf("\nEnter row %ld: ", row); - for (j = 0; j <= d; j++) { - lreadrat(&num[j], &den[j]); - lprat(" ", num[j], den[j]); - } - - lrs_set_row(P, Q, row, num, den, GE); - } - - printf("\nEnter objective row c_j j=1..%ld: ", d); - num[0] = 0; - den[0] = 1; - for (j = 1; j <= d; j++) { - lreadrat(&num[j], &den[j]); - lprat(" ", num[j], den[j]); - } - - lrs_set_obj(P, Q, num, den, MAXIMIZE); -} - -long readlinearity(lrs_dat *Q) /* read in and check linearity list */ -{ - long i, j; - long nlinearity; - if (fscanf(lrs_ifp, "%ld", &nlinearity) == EOF) { - fprintf(lrs_ofp, "\nLinearity option invalid, no indices "); - return (FALSE); - } - if (nlinearity < 1) { - fprintf(lrs_ofp, "\nLinearity option invalid, indices must be positive"); - return (FALSE); - } - - Q->linearity = (long int *)CALLOC((nlinearity + 1), sizeof(long)); - - for (i = 0; i < nlinearity; i++) { - if (fscanf(lrs_ifp, "%ld", &j) == EOF) { - fprintf(lrs_ofp, "\nLinearity option invalid, missing indices"); - return (FALSE); - } - Q->linearity[i] = j; - } - for (i = 1; i < nlinearity; i++) { /*sort in order */ - reorder(Q->linearity, nlinearity); - } - - Q->nlinearity = nlinearity; - Q->polytope = FALSE; - return TRUE; -} /* end readlinearity */ - -#ifdef PLRS -void plrs_readlinearity(lrs_dat *Q, string line) -{ - istringstream ss(line); - long nlinearity; - if (!(ss >> nlinearity)) { - printf("\nLinearity option invalid, no indices\n"); - exit(1); - } - if (nlinearity < 1) { - printf("\nLinearity option invalid, indices must be positive\n"); - exit(1); - } - - Q->linearity = (long int *)CALLOC((nlinearity + 1), sizeof(long)); - - for (int i = 0; i < nlinearity; i++) { - if (!(ss >> Q->linearity[i])) { - printf("\nLinearity option invalid, missing indices\n"); - exit(1); - } - } - - for (int i = 1; i < nlinearity; i++) { - reorder(Q->linearity, nlinearity); - } - - Q->nlinearity = nlinearity; - Q->polytope = FALSE; -} -#endif - -long readfacets(lrs_dat *Q, long facet[]) -/* read and check facet list for obvious errors during start/restart */ -/* this must be done after linearity option is processed!! */ -{ - long i, j; - /* assign local variables to structures */ - long m, d; - long *linearity = Q->linearity; - m = Q->m; - d = Q->inputd; - - for (j = Q->nlinearity; j < d; j++) /* note we place these after the linearity indices */ - { - if (fscanf(lrs_ifp, "%ld", &facet[j]) == EOF) { - fprintf(lrs_ofp, "\nrestart: facet list missing indices"); - return (FALSE); - } - - fprintf(lrs_ofp, " %ld", facet[j]); - /* 2010.4.26 nonnegative option needs larger range of indices */ - if (Q->nonnegative) { - if (facet[j] < 1 || facet[j] > m + d) { - fprintf(lrs_ofp, "\n Start/Restart cobasic indices must be in range 1 .. %ld ", m + d); - return FALSE; - } - } - if (!Q->nonnegative) { - if (facet[j] < 1 || facet[j] > m) { - fprintf(lrs_ofp, "\n Start/Restart cobasic indices must be in range 1 .. %ld ", m); - return FALSE; - } - } - for (i = 0; i < Q->nlinearity; i++) { - if (linearity[i] == facet[j]) { - fprintf(lrs_ofp, "\n Start/Restart cobasic indices should not include linearities"); - return FALSE; - } - } - /* bug fix 2011.8.1 reported by Steven Wu*/ - for (i = Q->nlinearity; i < j; i++) { - /* end bug fix 2011.8.1 */ - - if (facet[i] == facet[j]) { - fprintf(lrs_ofp, "\n Start/Restart cobasic indices must be distinct"); - return FALSE; - } - } - } - return TRUE; -} /* end of readfacets */ - -void printA(lrs_dic *P, lrs_dat *Q) /* print the integer m by n array A - with B,C,Row,Col vectors */ -{ - long i, j; - /* assign local variables to structures */ - lrs_mp_matrix A = P->A; - long *B = P->B; - long *C = P->C; - long *Row = P->Row; - long *Col = P->Col; - long m, d; - m = P->m; - d = P->d; - - fprintf(lrs_ofp, "\n Basis "); - for (i = 0; i <= m; i++) { - fprintf(lrs_ofp, "%ld ", B[i]); - } - fprintf(lrs_ofp, " Row "); - for (i = 0; i <= m; i++) { - fprintf(lrs_ofp, "%ld ", Row[i]); - } - fprintf(lrs_ofp, "\n Co-Basis "); - for (i = 0; i <= d; i++) { - fprintf(lrs_ofp, "%ld ", C[i]); - } - fprintf(lrs_ofp, " Column "); - for (i = 0; i <= d; i++) { - fprintf(lrs_ofp, "%ld ", Col[i]); - } - pmp(" det=", P->det); - fprintf(lrs_ofp, "\n"); - i = 0; - while (i <= m) { - for (j = 0; j <= d; j++) { - pimat(P, i, j, A[Row[i]][Col[j]], "A"); - } - fprintf(lrs_ofp, "\n"); - if (i == 0 && Q->nonnegative) { /* skip basic rows - don't exist! */ - i = d; - } - i++; - fflush(stdout); - } - fflush(stdout); -} - -void pimat(lrs_dic *P, long r, long s, lrs_mp Nt, char name[]) -/*print the long precision integer in row r col s of matrix A */ -{ - long *B = P->B; - long *C = P->C; - if (s == 0) { - fprintf(lrs_ofp, "%s[%ld][%ld]=", name, B[r], C[s]); - } - else { - fprintf(lrs_ofp, "[%ld]=", C[s]); - } - pmp("", Nt); -} - -/***************************************************************/ -/* */ -/* Routines for caching, allocating etc. */ -/* */ -/***************************************************************/ - -/* From here mostly Bremner's handiwork */ - -static void cache_dict(lrs_dic **D_p, lrs_dat *global, long i, long j) -{ - - if (dict_limit > 1) { - /* save row, column indicies */ - (*D_p)->i = i; - (*D_p)->j = j; - - /* Make a new, blank spot at the end of the queue to copy into */ - - pushQ(global, (*D_p)->m, (*D_p)->d, (*D_p)->m_A); - - copy_dict(global, global->Qtail, *D_p); /* Copy current dictionary */ - } - *D_p = global->Qtail; -} - -void copy_dict(lrs_dat *global, lrs_dic *dest, lrs_dic *src) -{ - long m = src->m; - long m_A = src->m_A; /* number of rows in A */ - long d = src->d; - long r, s; - -#ifdef GMP - for (r = 0; r <= m_A; r++) { - for (s = 0; s <= d; s++) { - copy(dest->A[r][s], src->A[r][s]); - } - } - -#else /* fast copy for MP and LRSLONG arithmetic */ - /* Note that the "A" pointer trees need not be copied, since they - always point to the same places within the corresponding space -*/ - /* I wish I understood the above remark. For the time being, do it the easy way for Nash */ - if (global->nash) { - for (r = 0; r <= m_A; r++) { - for (s = 0; s <= d; s++) { - copy(dest->A[r][s], src->A[r][s]); - } - } - } - else { - memcpy(dest->A[0][0], (global->Qtail->prev)->A[0][0], - (d + 1) * (lrs_digits + 1) * (m_A + 1) * sizeof(long)); - } - -#endif - - dest->i = src->i; - dest->j = src->j; - dest->m = m; - dest->d = d; - dest->m_A = src->m_A; - - dest->depth = src->depth; - dest->lexflag = src->lexflag; - - copy(dest->det, src->det); - copy(dest->objnum, src->objnum); - copy(dest->objden, src->objden); - - if (global->debug) { - fprintf(lrs_ofp, "\nSaving dict at depth %ld\n", src->depth); - } - - memcpy(dest->B, src->B, (m + 1) * sizeof(long)); - memcpy(dest->C, src->C, (d + 1) * sizeof(long)); - memcpy(dest->Row, src->Row, (m + 1) * sizeof(long)); - memcpy(dest->Col, src->Col, (d + 1) * sizeof(long)); -} - -/* - * pushQ(lrs_dat *globals,m,d): - * this routine ensures that Qtail points to a record that - * may be copied into. - * - * It may create a new record, or it may just move the head pointer - * forward so that know that the old record has been overwritten. - */ -#if 0 -#define TRACE(s) fprintf(stderr, "\n%s %p %p\n", s, global->Qhead, global->Qtail); -#else -#define TRACE(s) -#endif - -static void pushQ(lrs_dat *global, long m, long d, long m_A) -{ - - if ((global->Qtail->next) == global->Qhead) { - /* the Queue is full */ - if (dict_count < dict_limit) { - /* but we are allowed to create more */ - lrs_dic *p; - - p = new_lrs_dic(m, d, m_A); - - if (p) { - - /* we successfully created another record */ - - p->next = global->Qtail->next; - (global->Qtail->next)->prev = p; - (global->Qtail->next) = p; - p->prev = global->Qtail; - - dict_count++; - global->Qtail = p; - - TRACE("Added new record to Q"); - } - else { - /* virtual memory exhausted. bummer */ - global->Qhead = global->Qhead->next; - global->Qtail = global->Qtail->next; - - TRACE("VM exhausted"); - } - } - else { - /* - * user defined limit reached. start overwriting the - * beginning of Q - */ - global->Qhead = global->Qhead->next; - global->Qtail = global->Qtail->next; - TRACE("User limit"); - } - } - - else { - global->Qtail = global->Qtail->next; - TRACE("Reusing"); - } -} - -lrs_dic *lrs_getdic(lrs_dat *Q) -/* create another dictionary for Q without copying any values */ -/* derived from lrs_alloc_dic, used by nash.c */ -{ - lrs_dic *p; - - long m; - - m = Q->m; - - /* nonnegative flag set means that problem is d rows "bigger" */ - /* since nonnegative constraints are not kept explicitly */ - - if (Q->nonnegative) { - m = m + Q->inputd; - } - - p = new_lrs_dic(m, Q->inputd, Q->m); - if (!p) { - return NULL; - } - - p->next = p; - p->prev = p; - Q->Qhead = p; - Q->Qtail = p; - - return p; -} - -#define NULLRETURN(e) \ - if (!(e)) \ - return NULL; - -static lrs_dic *new_lrs_dic(long m, long d, long m_A) -{ - lrs_dic *p; - - NULLRETURN(p = (lrs_dic *)malloc(sizeof(lrs_dic))); - - NULLRETURN(p->B = (long int *)calloc((m + 1), sizeof(long))); - NULLRETURN(p->Row = (long int *)calloc((m + 1), sizeof(long))); - - NULLRETURN(p->C = (long int *)calloc((d + 1), sizeof(long))); - NULLRETURN(p->Col = (long int *)calloc((d + 1), sizeof(long))); - -#ifdef GMP - lrs_alloc_mp(p->det); - lrs_alloc_mp(p->objnum); - lrs_alloc_mp(p->objden); -#endif - - p->d_orig = d; - p->A = lrs_alloc_mp_matrix(m_A, d); - - return p; -} - -void lrs_free_dic(lrs_dic *P, lrs_dat *Q) -{ - /* do the same steps as for allocation, but backwards */ - /* gmp variables cannot be cleared using free: use lrs_clear_mp* */ - lrs_dic *P1; - - /* repeat until cache is empty */ - - do { - /* I moved these here because I'm not certain the cached dictionaries - need to be the same size. Well, it doesn't cost anything to be safe. db */ - - long d = P->d_orig; - long m_A = P->m_A; - - lrs_clear_mp_matrix(P->A, m_A, d); - - /* "it is a ghastly error to free something not assigned my malloc" KR167 */ - /* so don't try: free (P->det); */ - - lrs_clear_mp(P->det); - lrs_clear_mp(P->objnum); - lrs_clear_mp(P->objden); - - free(P->Row); - free(P->Col); - free(P->C); - free(P->B); - - /* go to next record in cache if any */ - P1 = P->next; - free(P); - P = P1; - - } while (Q->Qhead != P); -} - -void lrs_free_dic2(lrs_dic *P, lrs_dat *Q) -{ - /* do the same steps as for allocation, but backwards */ - /* same as lrs_free_dic except no cache for P */ - /* I moved these here because I'm not certain the cached dictionaries - need to be the same size. Well, it doesn't cost anything to be safe. db */ - - long d = P->d_orig; - long m_A = P->m_A; - - lrs_clear_mp_matrix(P->A, m_A, d); - - /* "it is a ghastly error to free something not assigned my malloc" KR167 */ - /* so don't try: free (P->det); */ - - printf("\n hello 2"); - fflush(stdout); - lrs_clear_mp(P->det); - lrs_clear_mp(P->objnum); - lrs_clear_mp(P->objden); - printf("\n hello 2"); - fflush(stdout); - - free(P->Row); - free(P->Col); - free(P->C); - free(P->B); - - printf("\n hello 2"); - fflush(stdout); - free(P); -} - -void lrs_free_dat(lrs_dat *Q) -{ - long m = Q->m; - - /* most of these items were allocated in lrs_alloc_dic */ - - lrs_clear_mp_vector(Q->Gcd, m); - lrs_clear_mp_vector(Q->Lcm, m); - - lrs_clear_mp(Q->sumdet); - lrs_clear_mp(Q->Nvolume); - lrs_clear_mp(Q->Dvolume); - lrs_clear_mp(Q->saved_det); - lrs_clear_mp(Q->boundd); - lrs_clear_mp(Q->boundn); - - free(Q->inequality); - free(Q->facet); - free(Q->redundcol); - free(Q->linearity); - free(Q->minratio); - free(Q->temparray); - - free(Q->name); - free(Q->saved_C); - - lrs_global_count--; - - free(Q); -} - -static long check_cache(lrs_dic **D_p, lrs_dat *global, long *i_p, long *j_p) -{ - /* assign local variables to structures */ - - cache_tries++; - - if (global->Qtail == global->Qhead) { - TRACE("cache miss"); - /* Q has only one element */ - cache_misses++; - return 0; - } - else { - global->Qtail = global->Qtail->prev; - - *D_p = global->Qtail; - - *i_p = global->Qtail->i; - *j_p = global->Qtail->j; - - TRACE("restoring dict"); - return 1; - } -} - -lrs_dic *lrs_alloc_dic(lrs_dat *Q) -/* allocate and initialize lrs_dic */ -{ - - lrs_dic *p; - long i, j; - long m, d, m_A; - - if (Q->hull) { /* d=col dimension of A */ - Q->inputd = Q->n; /* extra column for hull */ - } - else { - Q->inputd = Q->n - 1; - } - - m = Q->m; - d = Q->inputd; - m_A = m; /* number of rows in A */ - - /* nonnegative flag set means that problem is d rows "bigger" */ - /* since nonnegative constraints are not kept explicitly */ - - if (Q->nonnegative) { - m = m + d; - } - - p = new_lrs_dic(m, d, m_A); - if (!p) { - return NULL; - } - - p->next = p; - p->prev = p; - Q->Qhead = p; - Q->Qtail = p; - - dict_count = 1; - dict_limit = 50; - cache_tries = 0; - cache_misses = 0; - - /* Initializations */ - - p->d = p->d_orig = d; - p->m = m; - p->m_A = m_A; - p->depth = 0L; - p->lexflag = TRUE; - itomp(ONE, p->det); - itomp(ZERO, p->objnum); - itomp(ONE, p->objden); - - /*m+d+1 is the number of variables, labelled 0,1,2,...,m+d */ - /* initialize array to zero */ - for (i = 0; i <= m_A; i++) { - for (j = 0; j <= d; j++) { - itomp(ZERO, p->A[i][j]); - } - } - - Q->inequality = (long int *)CALLOC((m + 1), sizeof(long)); - if (Q->nlinearity == ZERO) { /* linearity may already be allocated */ - Q->linearity = (long int *)CALLOC((m + 1), sizeof(long)); - } - - Q->facet = (long int *)CALLOC((unsigned)d + 1, sizeof(long)); - Q->redundcol = (long int *)CALLOC((d + 1), sizeof(long)); - Q->minratio = (long int *)CALLOC((m + 1), sizeof(long)); - /* 2011.7.14 minratio[m]=0 for degen =1 for nondegen pivot*/ - Q->temparray = (long int *)CALLOC((unsigned)d + 1, sizeof(long)); - - Q->inequality[0] = 2L; - Q->Gcd = lrs_alloc_mp_vector(m); - Q->Lcm = lrs_alloc_mp_vector(m); - Q->saved_C = (long int *)CALLOC(d + 1, sizeof(long)); - - Q->lastdv = d; /* last decision variable may be decreased */ - /* if there are redundant columns */ - - /*initialize basis and co-basis indices, and row col locations */ - /*if nonnegative, we label differently to avoid initial pivots */ - /* set basic indices and rows */ - if (Q->nonnegative) { - for (i = 0; i <= m; i++) { - p->B[i] = i; - if (i <= d) { - p->Row[i] = 0; /* no row for decision variables */ - } - else { - p->Row[i] = i - d; - } - } - } - else { - for (i = 0; i <= m; i++) { - if (i == 0) { - p->B[0] = 0; - } - else { - p->B[i] = d + i; - } - p->Row[i] = i; - } - } - - for (j = 0; j < d; j++) { - if (Q->nonnegative) { - p->C[j] = m + j + 1; - } - else { - p->C[j] = j + 1; - } - p->Col[j] = j + 1; - } - p->C[d] = m + d + 1; - p->Col[d] = 0; - return p; -} /* end of lrs_alloc_dic */ - -/* - this routine makes a copy of the information needed to restart, - so that we can guarantee that if a signal is received, we - can guarantee that nobody is messing with it. - This as opposed to adding all kinds of critical regions in - the main line code. - - It is also used to make sure that in case of overflow, we - have a valid cobasis to restart from. - */ -static void save_basis(lrs_dic *P, lrs_dat *Q) -{ - int i; - /* assign local variables to structures */ - long *C = P->C; - long d; - -#ifdef SIGNALS - sigset_t oset, blockset; - sigemptyset(&blockset); - sigaddset(&blockset, SIGTERM); - sigaddset(&blockset, SIGHUP); - sigaddset(&blockset, SIGUSR1); - - errcheck("sigprocmask", sigprocmask(SIG_BLOCK, &blockset, &oset)); -#endif - d = P->d; - - Q->saved_flag = 1; - - for (i = 0; i < 3; i++) { - Q->saved_count[i] = Q->count[i]; - } - - for (i = 0; i < d + 1; i++) { - Q->saved_C[i] = C[i]; - } - - copy(Q->saved_det, P->det); - - Q->saved_d = P->d; - Q->saved_depth = P->depth; - -#ifdef SIGNALS - errcheck("sigprocmask", sigprocmask(SIG_SETMASK, &oset, 0)); -#endif -} - -/* digits overflow is a call from lrs_mp package */ - -void digits_overflow() -{ - fprintf(lrs_ofp, "\nOverflow at digits=%ld", DIG2DEC(lrs_digits)); - fprintf(lrs_ofp, "\nRerun with option: digits n, where n > %ld\n", DIG2DEC(lrs_digits)); - lrs_dump_state(); - - notimpl(""); -} - -static void lrs_dump_state() -{ - long i; - - fprintf(stderr, "\n\nlrs_lib: checkpointing:\n"); - - fprintf(stderr, "lrs_lib: Current digits at %ld out of %ld\n", DIG2DEC(lrs_record_digits), - DIG2DEC(lrs_digits)); - - for (i = 0; i < lrs_global_count; i++) { - print_basis(stderr, lrs_global_list[i]); - } - fprintf(stderr, "lrs_lib: checkpoint finished\n"); -} - -/* print out the saved copy of the basis */ -void print_basis(FILE *fp, lrs_dat *global) -{ - int i; - /* assign local variables to structures */ - fprintf(fp, "lrs_lib: State #%ld: (%s)\t", global->id, global->name); - - if (global->saved_flag) { - - fprintf(fp, "V#%ld R#%ld B#%ld h=%ld facets ", global->saved_count[1], global->saved_count[0], - global->saved_count[2], global->saved_depth); - for (i = 0; i < global->saved_d; i++) { - fprintf(fp, "%ld ", global->inequality[global->saved_C[i] - global->lastdv]); - } - pmp(" det=", global->saved_det); - fprintf(fp, "\n"); - } - else { - fprintf(fp, "lrs_lib: Computing initial basis\n"); - } - - fflush(fp); -} - -#ifdef SIGNALS - -/* - If given a signal - USR1 print current cobasis and continue - TERM print current cobasis and terminate - INT (ctrl-C) ditto - HUP ditto - */ -static void setup_signals() -{ - errcheck("signal", signal(SIGTERM, die_gracefully)); - errcheck("signal", signal(SIGALRM, timecheck)); - errcheck("signal", signal(SIGHUP, die_gracefully)); - errcheck("signal", signal(SIGINT, die_gracefully)); - errcheck("signal", signal(SIGUSR1, checkpoint)); -} - -static void timecheck() -{ - lrs_dump_state(); - errcheck("signal", signal(SIGALRM, timecheck)); - alarm(lrs_checkpoint_seconds); -} - -static void checkpoint() -{ - lrs_dump_state(); - errcheck("signal", signal(SIGUSR1, checkpoint)); -} - -static void die_gracefully() -{ - lrs_dump_state(); - - exit(1); -} - -#endif - -#ifdef TIMES -/* - * Not sure about the portability of this yet, - * - db - */ -#include -#define double_time(t) ((double)(t.tv_sec) + (double)(t.tv_usec) / 1000000) - -static void ptimes() -{ - struct rusage rusage; - getrusage(RUSAGE_SELF, &rusage); - fprintf(lrs_ofp, "\n*%0.3fu %0.3fs %ldKb %ld flts %ld swaps %ld blks-in %ld blks-out \n", - double_time(rusage.ru_utime), double_time(rusage.ru_stime), rusage.ru_maxrss, - rusage.ru_majflt, rusage.ru_nswap, rusage.ru_inblock, rusage.ru_oublock); - if (lrs_ofp != stdout) { - printf("\n*%0.3fu %0.3fs %ldKb %ld flts %ld swaps %ld blks-in %ld blks-out \n", - double_time(rusage.ru_utime), double_time(rusage.ru_stime), rusage.ru_maxrss, - rusage.ru_majflt, rusage.ru_nswap, rusage.ru_inblock, rusage.ru_oublock); - } -} - -static double get_time() -{ - struct rusage rusage; - getrusage(RUSAGE_SELF, &rusage); - return (double_time(rusage.ru_utime)); -} - -#endif - -/* Routines based on lp_solve */ - -void lrs_set_row(lrs_dic *P, lrs_dat *Q, long row, long num[], long den[], long ineq) -/* convert to lrs_mp then call lrs_set_row */ -{ - lrs_mp_vector Num, Den; - long d; - long j; - - d = P->d; - - Num = lrs_alloc_mp_vector(d + 1); - Den = lrs_alloc_mp_vector(d + 1); - - for (j = 0; j <= d; j++) { - itomp(num[j], Num[j]); - itomp(den[j], Den[j]); - } - - lrs_set_row_mp(P, Q, row, Num, Den, ineq); - - lrs_clear_mp_vector(Num, d + 1); - lrs_clear_mp_vector(Den, d + 1); -} - -void lrs_set_row_mp(lrs_dic *P, lrs_dat *Q, long row, lrs_mp_vector num, lrs_mp_vector den, - long ineq) -/* set row of dictionary using num and den arrays for rational input */ -/* ineq = 1 (GE) - ordinary row */ -/* = 0 (EQ) - linearity */ -{ - lrs_mp Temp, mpone; - lrs_mp_vector oD; /* denominator for row */ - - long i, j; - - /* assign local variables to structures */ - - lrs_mp_matrix A; - lrs_mp_vector Gcd, Lcm; - long hull; - long m, d; - lrs_alloc_mp(Temp); - lrs_alloc_mp(mpone); - hull = Q->hull; - A = P->A; - m = P->m; - d = P->d; - Gcd = Q->Gcd; - Lcm = Q->Lcm; - - oD = lrs_alloc_mp_vector(d); - itomp(ONE, mpone); - itomp(ONE, oD[0]); - - i = row; - itomp(ONE, Lcm[i]); /* Lcm of denominators */ - itomp(ZERO, Gcd[i]); /* Gcd of numerators */ - for (j = hull; j <= d; j++) /* hull data copied to cols 1..d */ - { - copy(A[i][j], num[j - hull]); - copy(oD[j], den[j - hull]); - if (!one(oD[j])) { - lcm(Lcm[i], oD[j]); /* update lcm of denominators */ - } - copy(Temp, A[i][j]); - gcd(Gcd[i], Temp); /* update gcd of numerators */ - } - - if (hull) { - itomp(ZERO, A[i][0]); /*for hull, we have to append an extra column of zeroes */ - if (!one(A[i][1]) || !one(oD[1])) { /* all rows must have a one in column one */ - Q->polytope = FALSE; - } - } - if (!zero(A[i][hull])) { /* for H-rep, are zero in column 0 */ - Q->homogeneous = FALSE; /* for V-rep, all zero in column 1 */ - } - - storesign(Gcd[i], POS); - storesign(Lcm[i], POS); - if (mp_greater(Gcd[i], mpone) || mp_greater(Lcm[i], mpone)) { - for (j = 0; j <= d; j++) { - exactdivint(A[i][j], Gcd[i], Temp); /*reduce numerators by Gcd */ - mulint(Lcm[i], Temp, Temp); /*remove denominators */ - exactdivint(Temp, oD[j], A[i][j]); /*reduce by former denominator */ - } - } - - if (ineq == EQ) /* input is linearity */ - { - Q->linearity[Q->nlinearity] = row; - Q->nlinearity++; - } - - /* 2010.4.26 Set Gcd and Lcm for the non-existant rows when nonnegative set */ - - if (Q->nonnegative && row == m) { - for (j = 1; j <= d; j++) { - itomp(ONE, Lcm[m + j]); - itomp(ONE, Gcd[m + j]); - } - } - - lrs_clear_mp_vector(oD, d); - lrs_clear_mp(Temp); - lrs_clear_mp(mpone); -} /* end of lrs_set_row_mp */ - -void lrs_set_obj(lrs_dic *P, lrs_dat *Q, long num[], long den[], long max) -{ - long i; - - if (max == MAXIMIZE) { - Q->maximize = TRUE; - } - else { - Q->minimize = TRUE; - for (i = 0; i <= P->d; i++) { - num[i] = -num[i]; - } - } - - lrs_set_row(P, Q, 0L, num, den, GE); -} - -void lrs_set_obj_mp(lrs_dic *P, lrs_dat *Q, lrs_mp_vector num, lrs_mp_vector den, long max) -{ - long i; - - if (max == MAXIMIZE) { - Q->maximize = TRUE; - } - else { - Q->minimize = TRUE; - for (i = 0; i <= P->d; i++) { - changesign(num[i]); - } - } - - lrs_set_row_mp(P, Q, 0L, num, den, GE); -} - -long lrs_solve_lp(lrs_dic *P, lrs_dat *Q) -/* user callable function to solve lp only */ -{ - lrs_mp_matrix Lin; /* holds input linearities if any are found */ - long col; - - Q->lponly = TRUE; - - if (!lrs_getfirstbasis(&P, Q, &Lin, FALSE)) { - return FALSE; - } - - /* There may have been column redundancy */ - /* If so the linearity space is obtained and redundant */ - /* columns are removed. User can access linearity space */ - /* from lrs_mp_matrix Lin dimensions nredundcol x d+1 */ - - for (col = 0; col < Q->nredundcol; col++) { /* print linearity space */ - lrs_printoutput(Q, Lin[col]); /* Array Lin[][] holds the coeffs. */ - } - - return TRUE; -} /* end of lrs_solve_lp */ - -long dan_selectpivot(lrs_dic *P, lrs_dat *Q, long *r, long *s) -/* select pivot indices using dantzig simplex method */ -/* largest coefficient with lexicographic rule to avoid cycling */ -/* Bohdan Kaluzny's handiwork */ -/* returns TRUE if pivot found else FALSE */ -/* pivot variables are B[*r] C[*s] in locations Row[*r] Col[*s] */ -{ - long j, k, col; - lrs_mp coeff; - /* assign local variables to structures */ - lrs_mp_matrix A = P->A; - long *Col = P->Col; - long d = P->d; - - lrs_alloc_mp(coeff); - *r = 0; - *s = d; - j = 0; - k = 0; - - itomp(0, coeff); - /*find positive cost coef */ - while (k < d) { - if (mp_greater(A[0][Col[k]], coeff)) { - j = k; - copy(coeff, A[0][Col[j]]); - } - k++; - } - - if (positive(coeff)) /* pivot column found! */ - { - *s = j; - col = Col[j]; - - /*find min index ratio */ - *r = lrs_ratio(P, Q, col); - if (*r != 0) { - lrs_clear_mp(coeff); - return (TRUE); /* unbounded */ - } - } - lrs_clear_mp(coeff); - return (FALSE); -} /* end of dan_selectpivot */ - -long phaseone(lrs_dic *P, lrs_dat *Q) -/* Do a dual pivot to get primal feasibility (pivot in X_0)*/ -/* Bohdan Kaluzny's handiwork */ -{ - long i, j, k; - /* assign local variables to structures */ - lrs_mp_matrix A = P->A; - long *Row = P->Row; - long *Col = P->Col; - long m, d; - lrs_mp b_vector; - lrs_alloc_mp(b_vector); - m = P->m; - d = P->d; - i = 0; - k = d + 1; - - itomp(0, b_vector); - - fprintf(lrs_ofp, "\nLP: Phase One: Dual pivot on artificial variable"); - - /*find most negative b vector */ - while (k <= m) { - if (mp_greater(b_vector, A[Row[k]][0])) { - i = k; - copy(b_vector, A[Row[i]][0]); - } - k++; - } - - if (negative(b_vector)) /* pivot row found! */ - { - j = 0; /*find a positive entry for in row */ - while (j < d && !positive(A[Row[i]][Col[j]])) { - j++; - } - if (j >= d) { - lrs_clear_mp(b_vector); - return (FALSE); /* no positive entry */ - } - pivot(P, Q, i, j); - update(P, Q, &i, &j); - } - lrs_clear_mp(b_vector); - return (TRUE); -} - -long lrs_set_digits(long dec_digits) -{ - /* convert user specified decimal digits to mp digits */ - - fprintf(lrs_ofp, "\n*digits %ld", dec_digits); - if (dec_digits > 0) { - lrs_digits = DEC2DIG(dec_digits); - } - if (lrs_digits > MAX_DIGITS) { - fprintf(lrs_ofp, "\nDigits must be at most %ld\nChange MAX_DIGITS and recompile", - DIG2DEC(MAX_DIGITS)); - fflush(stdout); - return (FALSE); - } - return (TRUE); -} - -long lrs_checkbound(lrs_dic *P, lrs_dat *Q) -{ - /* check bound on objective and return TRUE if exceeded */ - - if (!Q->bound) { - return FALSE; - } - - if (Q->maximize && comprod(Q->boundn, P->objden, P->objnum, Q->boundd) == 1) { -#ifndef PLRS - if (Q->verbose) { - prat(" \nObj value: ", P->objnum, P->objden); - fprintf(lrs_ofp, " Pruning "); - } -#endif - return TRUE; - } - if (Q->minimize && comprod(Q->boundn, P->objden, P->objnum, Q->boundd) == -1) { -#ifndef PLRS - if (Q->verbose) { - prat(" \nObj value: ", P->objnum, P->objden); - fprintf(lrs_ofp, " Pruning "); - } -#endif - return TRUE; - } - return FALSE; -} - -long lrs_leaf(lrs_dic *P, lrs_dat *Q) -{ - /* check if current dictionary is a leaf of reverse search tree */ - long col = 0; - long tmp = 0; - - while (col < P->d && !reverse(P, Q, &tmp, col)) { - col++; - } - if (col < P->d) { - return 0; /* dictionary is not a leaf */ - } - else { - return 1; - } -} diff --git a/src/solvers/lrs/lrslib.h b/src/solvers/lrs/lrslib.h deleted file mode 100644 index 13b3871c8..000000000 --- a/src/solvers/lrs/lrslib.h +++ /dev/null @@ -1,354 +0,0 @@ -/* lrslib.hpp (vertex enumeration using lexicographic reverse search) */ -#define TITLE "lrslib " -#define LRS_VERSION "v.6.2 2016.3.28" -#define AUTHOR "*Copyright (C) 1995,2016, David Avis avis@cs.mcgill.ca " - -/* 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., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. - */ -/*Ver 6.1 major change is new lrsnash driver and library coded by Terje Lensberg */ -/*Ver 6.0 major change is mplrs wrapper for multithreading coded by Skip Jordan */ -/*Ver 5.0 major change is plrs wrapper for multithreading coded by Gary Roumanis */ -/*Ver 4.2* library version */ -/******************************************************************************/ -/* See http://cgm.cs.mcgill.ca/~avis/C/lrs.html for usage instructions */ -/******************************************************************************/ - -#ifdef PLRS -#include -#include -#include -#include -#include -#endif - -#ifdef LRSLONG -#define ARITH "lrslong.h" /* lrs long integer arithmetic package */ -#else -#ifdef GMP -#define ARITH "lrsgmp.h" /* lrs wrapper for gmp multiple precsion arithmetic */ -#else -#define ARITH "lrsmp.h" /* lrs multiple precsion arithmetic */ -#define MP -#endif -#endif - -#include ARITH - -#ifdef SIGNALS -#include -#include -#define errcheck(s, e) \ - if ((long)(e) == -1L) { \ - perror(s); \ - exit(1); \ - } -#endif - -#define CALLOC(n, s) xcalloc(n, s, __LINE__, __FILE__) - -/*********************/ -/*global constants */ -/*********************/ -#define MAX_LRS_GLOBALS 10000L /* number of allocated dictionaries */ -#define MAXIMIZE 1L /* maximize the lp */ -#define MINIMIZE 0L /* maximize the lp */ -#define GE 1L /* constraint is >= */ -#define EQ 0L /* constraint is linearity */ - -/*************/ -/* typedefs */ -/*************/ - -/******************************************************************************/ -/* Indexing after initialization */ -/* Basis Cobasis */ -/* --------------------------------------- ----------------------------- */ -/* | i |0|1| .... |lastdv|lastdv+1|...|m| | j | 0 | 1 | ... |d-1| d | */ -/* |-----|+|+|++++++|++++++|--------|---|-| |----|---|---|-----|---|+++++| */ -/* |B[i] |0|1| .... |lastdv|lastdv+1|...|m| |C[j]|m+1|m+2| ... |m+d|m+d+1| */ -/* -----|+|+|++++++|++++++|????????|???|?| ----|???|???|-----|???|+++++| */ -/* */ -/* Row[i] is row location for B[i] Col[j] is column location for C[j] */ -/* ----------------------------- ----------------------------- */ -/* | i |0|1| ..........|m-1|m| | j | 0 | 1 | ... |d-1| d | */ -/* |-------|+|-|-----------|---|-| |------|---|---|--- |---|++++| */ -/* |Row[i] |0|1|...........|m-1|m| |Col[j]| 1 | 2 | ... | d | 0 | */ -/* --------|+|*|***********|***|*| ------|***|***|*****|***|++++| */ -/* */ -/* + = remains invariant * = indices may be permuted ? = swapped by pivot */ -/* */ -/* m = number of input rows n= number of input columns */ -/* input dimension inputd = n-1 (H-rep) or n (V-rep) */ -/* lastdv = inputd-nredundcol (each redundant column removes a dec. var) */ -/* working dimension d=lastdv-nlinearity (an input linearity removes a slack) */ -/* obj function in row 0, index 0=B[0] col 0 has index m+d+1=C[d] */ -/* H-rep: b-vector in col 0, A matrix in columns 1..n-1 */ -/* V-rep: col 0 all zero, b-vector in col 1, A matrix in columns 1..n */ -/******************************************************************************/ - -typedef struct lrs_dic_struct /* dynamic dictionary data */ -{ - lrs_mp_matrix A; - long m; /* A has m+1 rows, row 0 is cost row */ - long m_A; /* =m or m-d if nonnegative flag set */ - long d; /* A has d+1 columns, col 0 is b-vector */ - long d_orig; /* value of d as A was allocated (E.G.) */ - long lexflag; /* true if lexmin basis for this vertex */ - long depth; /* depth of basis/vertex in reverse search tree */ - long i, j; /* last pivot row and column pivot indices */ - lrs_mp det; /* current determinant of basis */ - lrs_mp objnum; /* objective numerator value */ - lrs_mp objden; /* objective denominator value */ - long *B, *Row; /* basis, row location indices */ - long *C, *Col; /* cobasis, column location indices */ - struct lrs_dic_struct *prev, *next; -} lrs_dic; - -typedef struct lrs_dat /* global problem data */ -{ - lrs_mp_vector Gcd; /* Gcd of each row of numerators */ - lrs_mp_vector Lcm; /* Lcm for each row of input denominators */ - - lrs_mp sumdet; /* sum of determinants */ - lrs_mp Nvolume; /* volume numerator */ - lrs_mp Dvolume; /* volume denominator */ - lrs_mp boundn; /* objective bound numerator */ - lrs_mp boundd; /* objective bound denominator */ - long unbounded; /* lp unbounded */ - char fname[100]; /* input file name from line 1 of input */ - - long *inequality; /* indices of inequalities corr. to cobasic ind */ - /* initially holds order used to find starting */ - /* basis, default: m,m-1,...,2,1 */ - long *facet; /* cobasic indices for restart in needed */ - long *redundcol; /* holds columns which are redundant */ - long *linearity; /* holds cobasic indices of input linearities */ - long *minratio; /* used for lexicographic ratio test */ - long *temparray; /* for sorting indices, dimensioned to d */ - long *isave, *jsave; /* arrays for estimator, malloc'ed at start */ - long inputd; /* input dimension: n-1 for H-rep, n for V-rep */ - - long m; /* number of rows in input file */ - long n; /* number of columns in input file */ - long lastdv; /* index of last dec. variable after preproc */ - /* given by inputd-nredundcol */ - long count[10]; /* count[0]=rays [1]=verts. [2]=base [3]=pivots */ - /* count[4]=integer vertices */ - - long startcount[5]; - - long deepest; /* max depth ever reached in search */ - long nredundcol; /* number of redundant columns */ - long nlinearity; /* number of input linearities */ - long totalnodes; /* count total number of tree nodes evaluated */ - long runs; /* probes for estimate function */ - long seed; /* seed for random number generator */ - double cest[10]; /* ests: 0=rays,1=vert,2=bases,3=vol,4=int vert */ - /**** flags ********** */ - long allbases; /* TRUE if all bases should be printed */ - long bound; /* TRUE if upper/lower bound on objective given */ - long countonly; /* TRUE if only count totals should be output */ - long debug; - long dualdeg; /* TRUE if start dictionary is dual degenerate */ - long etrace; /* turn off debug at basis # strace */ - long frequency; /* frequency to print cobasis indices */ - long geometric; /* TRUE if incident vertex prints after each ray */ - long getvolume; /* do volume calculation */ - long givenstart; /* TRUE if a starting cobasis is given */ - long homogeneous; /* TRUE if all entries in column one are zero */ - long hull; /* do convex hull computation if TRUE */ - long incidence; /* print all tight inequalities (vertices/rays) */ - long lponly; /* true if only lp solution wanted */ - long maxdepth; /* max depth to search to in treee */ - long maximize; /* flag for LP maximization */ - long maxoutput; /* if positive, maximum number of output lines */ - long maxcobases; /* if positive, after maxcobasis unexplored subtrees reported */ - long minimize; /* flag for LP minimization */ - long mindepth; /* do not backtrack above mindepth */ - long nash; /* TRUE for computing nash equilibria */ - long nonnegative; /* TRUE if last d constraints are nonnegativity */ - long polytope; /* TRUE for facet computation of a polytope */ - long printcobasis; /* TRUE if all cobasis should be printed */ - long printslack; /* TRUE if indices of slack inequal. printed */ - long truncate; /* TRUE: truncate tree when moving from opt vert*/ - long verbose; /* FALSE for minimalist output */ - long restart; /* TRUE if restarting from some cobasis */ - long strace; /* turn on debug at basis # strace */ - long voronoi; /* compute voronoi vertices by transformation */ - long subtreesize; /* in estimate mode, iterates if cob_est >= subtreesize */ - - /* Variables for saving/restoring cobasis, db */ - - long id; /* numbered sequentially */ - char *name; /* passed by user */ - - long saved_count[3]; /* How often to print out current cobasis */ - long *saved_C; - lrs_mp saved_det; - long saved_depth; - long saved_d; - - long saved_flag; /* There is something in the saved cobasis */ - - /* Variables for cacheing dictionaries, db */ - lrs_dic *Qhead, *Qtail; - -} lrs_dat, lrs_dat_p; - -#ifdef PLRS -/****************/ -/* PLRS */ -/****************/ - -void post_output(const char *, const char *); -void plrs_read_dat(lrs_dat *Q, std::ifstream &ff); -void plrs_read_dic(lrs_dic *P, lrs_dat *Q, std::ifstream &ff); -void plrs_readfacets(lrs_dat *Q, long facet[], string facets); -void plrs_readlinearity(lrs_dat *Q, string line); -#endif - -/*******************************/ -/* functions for external use */ -/*******************************/ -extern FILE *lrs_cfp; /* output file for checkpoint information */ -long lrs_main(int argc, char *argv[]); /* lrs driver, argv[1]=input file, [argc-1]=output file */ -long redund_main(int argc, char *argv[]); /* redund driver, argv[1]=input file, [2]=output file */ -lrs_dat *lrs_alloc_dat(const char *name); /* allocate for lrs_dat structure "name" */ -lrs_dic *lrs_alloc_dic(lrs_dat *Q); /* allocate for lrs_dic structure corr. to Q */ -long lrs_estimate( - lrs_dic *P, lrs_dat *Q); /* get estimates only and returns est number of cobases in subtree */ -long lrs_read_dat(lrs_dat *Q, int argc, char *argv[]); /* read header and set up lrs_dat */ -long lrs_read_dic(lrs_dic *P, lrs_dat *Q); /* read input and set up problem and lrs_dic */ -long lrs_checkbound(lrs_dic *P, - lrs_dat *Q); /* TRUE if current objective value exceeds specified bound */ -long lrs_getfirstbasis(lrs_dic **P_p, lrs_dat *Q, lrs_mp_matrix *Lin, - long no_output); /* gets first basis, FALSE if none,P may get changed if - lin. space Lin found no_output is TRUE supresses output - headers P may get changed if lin. space Lin found */ -void lrs_getinput(lrs_dic *P, lrs_dat *Q, long *num, long *den, long m, - long d); /* reads input matrix b A in lrs/cdd format */ -long lrs_getnextbasis( - lrs_dic **dict_p, lrs_dat *Q, - long prune); /* gets next lrs tree basis, FALSE if none backtrack if prune is TRUE */ -long lrs_getsolution(lrs_dic *P, lrs_dat *Q, lrs_mp_vector output, long col); -long lrs_getray(lrs_dic *P, lrs_dat *Q, long col, long comment, lrs_mp_vector output); -long lrs_getvertex(lrs_dic *P, lrs_dat *Q, lrs_mp_vector output); -void lrs_close(const char *name); /* close lrs lib program "name" */ -long lrs_init(const char *name); /* initialize lrslib and arithmetic package for prog "name" */ -void lrs_lpoutput(lrs_dic *P, lrs_dat *Q, - lrs_mp_vector output); /* print LP primal and dual solutions */ -void lrs_printcobasis(lrs_dic *P, lrs_dat *Q, - long col); /* print cobasis for column col(verted or ray) */ -void lrs_printoutput(lrs_dat *Q, lrs_mp_vector output); /* print output array */ -void lrs_printrow(char name[], lrs_dat *Q, lrs_mp_vector output, - long rowd); /*print row of A matrix in output[0..rowd] */ -void lrs_printsol(lrs_dic *P, lrs_dat *Q, long col, - long comment); /* print out solution from col, comment= 0=normal,-1=geometric - ray,1..inputd=linearity */ -void lrs_printtotals(lrs_dic *P, lrs_dat *Q); /* print final totals for lrs */ -long lrs_set_digits(long dec_digits); /* set lrsmp digits to equiv. of decimal dec_digits */ -long lrs_solvelp(lrs_dic *P, lrs_dat *Q, - long maximize); /* solve primal feas LP:TRUE bounded else FALSE */ - -/*******************************/ -/* functions for internal use */ -/*******************************/ - -/*******************************/ -/* basic dictionary functions */ -/*******************************/ -long getabasis(lrs_dic *P, lrs_dat *Q, long order[]); /* Try to find a starting basis */ -void getnextoutput(lrs_dic *P, lrs_dat *Q, long i, long col, - lrs_mp out); /* get A[B[i][col] and copy to out */ -long ismin(lrs_dic *P, lrs_dat *Q, long r, long s); /* test if A[r][s] is a min ratio for col s */ -long lexmin(lrs_dic *P, lrs_dat *Q, long col); /* test A to see if current basis is lexmin */ -void pivot(lrs_dic *P, lrs_dat *Q, long bas, long cob); /* Qpivot routine for array A */ -long primalfeasible(lrs_dic *P, lrs_dat *Q); /* Do dual pivots to get primal feasibility */ -long lrs_ratio(lrs_dic *P, lrs_dat *Q, long col); /* find lex min. ratio */ -long removecobasicindex(lrs_dic *P, lrs_dat *Q, long k); /* remove C[k] from problem */ -long restartpivots(lrs_dic *P, lrs_dat *Q); /* restart problem from given cobasis */ -long reverse(lrs_dic *P, lrs_dat *Q, long *r, - long s); /* TRUE if B[*r] C[s] is a reverse lex-pos pivot */ -long selectpivot(lrs_dic *P, lrs_dat *Q, long *r, - long *s); /* select pivot indices using lexicographic rule */ -long dan_selectpivot(lrs_dic *P, lrs_dat *Q, long *r, - long *s); /* select pivot indices using dantzig-lex rule */ -void update(lrs_dic *P, lrs_dat *Q, long *i, - long *j); /* update the B,C, LOC arrays after a pivot */ -void updatevolume(lrs_dic *P, lrs_dat *Q); /* rescale determinant and update the volume */ - -/*******************************/ -/* other functions using P,Q */ -/*******************************/ -long lrs_degenerate(lrs_dic *P, lrs_dat *Q); /* TRUE if the dictionary is primal degenerate */ -void print_basis(FILE *fp, lrs_dat *Q); -void printA(lrs_dic *P, lrs_dat *Q); /* raw print of dictionary, bases for debugging */ -void pimat(lrs_dic *P, long r, long s, lrs_mp Nt, char name[]); /* print the row r col s of A */ -long readfacets(lrs_dat *Q, long facet[]); /* read and check facet list */ -long readlinearity(lrs_dat *Q); /* read and check linearity list */ -void rescaledet(lrs_dic *P, lrs_dat *Q, lrs_mp Vnum, - lrs_mp Vden); /* rescale determinant to get its volume */ -void rescalevolume(lrs_dic *P, lrs_dat *Q, lrs_mp Vnum, - lrs_mp Vden); /* adjust volume for dimension */ -long lrs_leaf(lrs_dic *P, - lrs_dat *Q); /* true if current dictionary is leaf of reverse search tree */ - -/***************************************************/ -/* Routines for redundancy checking */ -/***************************************************/ -long checkredund(lrs_dic *P, lrs_dat *Q); /* solve primal lp to check redund of obj fun. returns - TRUE if redundant, else FALSE */ -long checkcobasic(lrs_dic *P, lrs_dat *Q, - long index); /* TRUE if index is cobasic and nondegenerate FALSE if basic, or - degen. cobasic, where it will get pivoted out */ -long checkindex(lrs_dic *P, lrs_dat *Q, - long index); /* index=0 non-red.,1 red., 2 input linearity NOTE: row is returned - all zero if redundant!! */ - -/***************************************************/ -/* Routines for caching and restoring dictionaries */ -/***************************************************/ -void lrs_free_dic(lrs_dic *P, lrs_dat *Q); -void lrs_free_dic2(lrs_dic *P, lrs_dat *Q); /* same as lrs_free_dic but no cache*/ -void lrs_free_dat(lrs_dat *Q); -void copy_dict(lrs_dat *global, lrs_dic *dest, lrs_dic *src); -lrs_dic *alloc_memory(lrs_dat *Q); -lrs_dic *lrs_getdic(lrs_dat *Q); -lrs_dic *resize(lrs_dic *P, lrs_dat *Q); - -/*******************************/ -/* utilities */ -/*******************************/ -void lprat(const char *name, long Num, long Den); /* Print Num/Den without reducing */ -long lreadrat(long *Num, long *Den); /* read a rational string and convert to long integers */ -void reorder(long a[], - long range); /* reorder array in increasing order with one misplaced element */ -void reorder1(long a[], long b[], long newone, - long range); /* reorder array a in increasing order with misplaced element newone - elements of b go along for the ride */ - -/***************************/ -/* lp_solve like functions */ -/***************************/ -long lrs_solve_lp(lrs_dic *P, lrs_dat *Q); /* solve lp only for given dictionary */ -void lrs_set_row(lrs_dic *P, lrs_dat *Q, long row, long num[], long den[], - long ineq); /* load row i of dictionary from num[]/den[] ineq=GE */ -void lrs_set_row_mp(lrs_dic *P, lrs_dat *Q, long row, lrs_mp_vector num, lrs_mp_vector den, - long ineq); /* same as lrs_set_row except num/den is lrs_mp type */ -void lrs_set_obj( - lrs_dic *P, lrs_dat *Q, long num[], long den[], - long max); /* set up objective function with coeffs num[]/den[] max=MAXIMIZE or MINIMIZE */ -void lrs_set_obj_mp(lrs_dic *P, lrs_dat *Q, lrs_mp_vector num, lrs_mp_vector den, - long max); /* same as lrs_set_obj but num/den has lrs_mp type */ diff --git a/src/solvers/lrs/lrsmp.c b/src/solvers/lrs/lrsmp.c deleted file mode 100644 index da8a54a8d..000000000 --- a/src/solvers/lrs/lrsmp.c +++ /dev/null @@ -1,1078 +0,0 @@ -/* lrsmp.c library code for lrs extended precision arithmetic */ -/* Version 4.0c, August 26, 2009 */ -/* minor change to check result of fscanf */ -/* Copyright: David Avis 1999, avis@cs.mcgill.ca */ - -/* 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., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. - */ - -#ifdef PLRS -#include -#include -#endif - -#include -#include -#include -#include "lrsmp.h" - -long lrs_digits; /* max permitted no. of digits */ -long lrs_record_digits; /* this is the biggest acheived so far. */ - -/******************************************************************/ -/* digit overflow is caught by digits_overflow at the end of this */ -/* file, make sure it is either user supplied or uncomment */ -/* the define below */ -/******************************************************************/ - -#define digits_overflow() lrs_default_digits_overflow() - -/*********************************************************/ -/* Initialization and allocation procedures - must use! */ -/******************************************************* */ - -long lrs_mp_init(long dec_digits, FILE *fpin, FILE *fpout) -/* max number of decimal digits for the computation */ -{ - /* global variables lrs_ifp and lrs_ofp are file pointers for input and output */ - - lrs_ifp = fpin; - lrs_ofp = fpout; - - lrs_record_digits = 0; - if (dec_digits <= 0) { - dec_digits = DEFAULT_DIGITS; - } - - lrs_digits = DEC2DIG(dec_digits); /* max permitted no. of digits */ - - if (lrs_digits > MAX_DIGITS) { -#ifdef PLRS - cout << "Digits must be at most " << DIG2DEC(MAX_DIGITS) << endl; - cout << "Change MAX_DIGITS and recompile" << endl; - exit(1); -#else - fprintf(lrs_ofp, "\nDigits must be at most %ld\nChange MAX_DIGITS and recompile\n", - DIG2DEC(MAX_DIGITS)); -#endif - lrs_digits = MAX_DIGITS; - return FALSE; - } - - return TRUE; -} - -lrs_mp_t lrs_alloc_mp_t() -/* dynamic allocation of lrs_mp number */ -{ - lrs_mp_t p; - p = (long *)calloc(lrs_digits + 1, sizeof(long)); - return p; -} - -lrs_mp_vector lrs_alloc_mp_vector(long n) -/* allocate lrs_mp_vector for n+1 lrs_mp numbers */ -{ - lrs_mp_vector p; - long i; - - p = (lrs_mp_vector)CALLOC((n + 1), sizeof(lrs_mp *)); - for (i = 0; i <= n; i++) { - p[i] = (long int *)CALLOC(1, sizeof(lrs_mp)); - } - - return p; -} - -void lrs_clear_mp_vector(lrs_mp_vector p, long n) -/* free space allocated to p */ -{ - long i; - for (i = 0; i <= n; i++) { - free(p[i]); - } - free(p); -} - -lrs_mp_matrix lrs_alloc_mp_matrix(long m, long n) -/* allocate lrs_mp_matrix for m+1 x n+1 lrs_mp numbers */ -{ - lrs_mp_matrix a; - long *araw; - int mp_width, row_width; - int i, j; - - mp_width = lrs_digits + 1; - row_width = (n + 1) * mp_width; - - araw = (long int *)calloc((m + 1) * row_width, sizeof(long)); - a = (lrs_mp_matrix)calloc((m + 1), sizeof(lrs_mp_vector)); - - for (i = 0; i < m + 1; i++) { - a[i] = (long int **)calloc((n + 1), sizeof(lrs_mp *)); - - for (j = 0; j < n + 1; j++) { - a[i][j] = (araw + i * row_width + j * mp_width); - } - } - return a; -} - -void lrs_clear_mp_matrix(lrs_mp_matrix p, long m, long n) -/* free space allocated to lrs_mp_matrix p */ -{ - long i; - - /* p[0][0] is araw, the actual matrix storage address */ - - free(p[0][0]); - - for (i = 0; i < m + 1; i++) { - free(p[i]); - } - free(p); -} - -/*********************************************************/ -/* Core library functions - depend on mp implementation */ -/******************************************************* */ - -void copy(lrs_mp a, lrs_mp b) /* assigns a=b */ -{ - long i; - for (i = 0; i <= length(b); i++) { - a[i] = b[i]; - } -} - -/********************************************************/ -/* Divide two multiple precision integers (c=a/b). */ -/* a is destroyed and contains the remainder on return. */ -/* From Knuth Vol.2 SemiNumerical Algorithms */ -/* coded by J. Quinn */ -/********************************************************/ -void divint(lrs_mp a, lrs_mp b, lrs_mp c) /* c=a/b, a contains remainder on return */ -{ - long cy, la, lb, lc, d1, s, t, sig; - long i, j, qh; - - /* figure out and save sign, do everything with positive numbers */ - sig = sign(a) * sign(b); - - la = length(a); - lb = length(b); - lc = la - lb + 2; - if (la < lb) { - storelength(c, TWO); - storesign(c, POS); - c[1] = 0; - normalize(c); - return; - } - for (i = 1; i < lc; i++) { - c[i] = 0; - } - storelength(c, lc); - storesign(c, (sign(a) == sign(b)) ? POS : NEG); - - /******************************/ - /* division by a single word: */ - /* do it directly */ - /******************************/ - - if (lb == 2) { - cy = 0; - t = b[1]; - for (i = la - 1; i > 0; i--) { - cy = cy * BASE + a[i]; - a[i] = 0; - cy -= (c[i] = cy / t) * t; - } - a[1] = cy; - storesign(a, (cy == 0) ? POS : sign(a)); - storelength(a, TWO); - /* set sign of c to sig (**mod**) */ - storesign(c, sig); - normalize(c); - return; - } - else { - /* mp's are actually DIGITS+1 in length, so if length of a or b = */ - /* DIGITS, there will still be room after normalization. */ - /****************************************************/ - /* Step D1 - normalize numbers so b > floor(BASE/2) */ - d1 = BASE / (b[lb - 1] + 1); - if (d1 > 1) { - cy = 0; - for (i = 1; i < la; i++) { - cy = (a[i] = a[i] * d1 + cy) / BASE; - a[i] %= BASE; - } - a[i] = cy; - cy = 0; - for (i = 1; i < lb; i++) { - cy = (b[i] = b[i] * d1 + cy) / BASE; - b[i] %= BASE; - } - b[i] = cy; - } - else { - a[la] = 0; /* if la or lb = DIGITS this won't work */ - b[lb] = 0; - } - /*********************************************/ - /* Steps D2 & D7 - start and end of the loop */ - for (j = 0; j <= la - lb; j++) { - /*************************************/ - /* Step D3 - determine trial divisor */ - if (a[la - j] == b[lb - 1]) { - qh = BASE - 1; - } - else { - s = (a[la - j] * BASE + a[la - j - 1]); - qh = s / b[lb - 1]; - while (qh * b[lb - 2] > (s - qh * b[lb - 1]) * BASE + a[la - j - 2]) { - qh--; - } - } - /*******************************************************/ - /* Step D4 - divide through using qh as quotient digit */ - cy = 0; - for (i = 1; i <= lb; i++) { - s = qh * b[i] + cy; - a[la - j - lb + i] -= s % BASE; - cy = s / BASE; - if (a[la - j - lb + i] < 0) { - a[la - j - lb + i] += BASE; - cy++; - } - } - /*****************************************************/ - /* Step D6 - adjust previous step if qh is 1 too big */ - if (cy) { - qh--; - cy = 0; - for (i = 1; i <= lb; i++) /* add a back in */ - { - a[la - j - lb + i] += b[i] + cy; - cy = a[la - j - lb + i] / BASE; - a[la - j - lb + i] %= BASE; - } - } - /***********************************************************************/ - /* Step D5 - write final value of qh. Saves calculating array indices */ - /* to do it here instead of before D6 */ - - c[la - lb - j + 1] = qh; - } - /**********************************************************************/ - /* Step D8 - unnormalize a and b to get correct remainder and divisor */ - - for (i = lc; c[i - 1] == 0 && i > 2; i--) - ; /* strip excess 0's from quotient */ - storelength(c, i); - if (i == 2 && c[1] == 0) { - storesign(c, POS); - } - cy = 0; - for (i = lb - 1; i >= 1; i--) { - cy = (a[i] += cy * BASE) % d1; - a[i] /= d1; - } - for (i = la; a[i - 1] == 0 && i > 2; i--) - ; /* strip excess 0's from quotient */ - storelength(a, i); - if (i == 2 && a[1] == 0) { - storesign(a, POS); - } - if (cy) { - fprintf(stdout, "divide error"); - exit(1); - } - for (i = lb - 1; i >= 1; i--) { - cy = (b[i] += cy * BASE) % d1; - b[i] /= d1; - } - } -} -/* end of divint */ - -void gcd(lrs_mp u, lrs_mp v) /*returns u=gcd(u,v) destroying v */ - /*Euclid's algorithm. Knuth, II, p.320 - modified to avoid copies r=u,u=v,v=r - Switches to single precision when possible for greater speed */ -{ - lrs_mp r; - unsigned long ul, vl; - long i; - static unsigned long maxspval = MAXD; /* Max value for the last digit to guarantee */ - /* fitting into a single long integer. */ - - static long maxsplen; /* Maximum digits for a number that will fit */ - /* into a single long integer. */ - - static long firstime = TRUE; - - if (firstime) /* initialize constants */ - { - for (maxsplen = 2; maxspval >= BASE; maxsplen++) { - maxspval /= BASE; - } - firstime = FALSE; - } - if (mp_greater(v, u)) { - goto bigv; - } -bigu: - if (zero(v)) { - return; - } - if ((i = length(u)) < maxsplen || (i == maxsplen && u[maxsplen - 1] < maxspval)) { - goto quickfinish; - } - divint(u, v, r); - normalize(u); - -bigv: - if (zero(u)) { - copy(u, v); - return; - } - if ((i = length(v)) < maxsplen || (i == maxsplen && v[maxsplen - 1] < maxspval)) { - goto quickfinish; - } - divint(v, u, r); - normalize(v); - goto bigu; - /* Base 10000 only at the moment */ - /* when u and v are small enough, transfer to single precision integers */ - /* and finish with euclid's algorithm, then transfer back to lrs_mp */ -quickfinish: - ul = vl = 0; - for (i = length(u) - 1; i > 0; i--) { - ul = BASE * ul + u[i]; - } - for (i = length(v) - 1; i > 0; i--) { - vl = BASE * vl + v[i]; - } - if (ul > vl) { - goto qv; - } -qu: - if (!vl) { - for (i = 1; ul; i++) { - u[i] = ul % BASE; - ul = ul / BASE; - } - storelength(u, i); - return; - } - ul %= vl; -qv: - if (!ul) { - for (i = 1; vl; i++) { - u[i] = vl % BASE; - vl = vl / BASE; - } - storelength(u, i); - return; - } - vl %= ul; - goto qu; -} - -long compare(lrs_mp a, lrs_mp b) /* a ? b and returns -1,0,1 for <,=,> */ -{ - long i; - - if (a[0] > b[0]) { - return 1L; - } - if (a[0] < b[0]) { - return -1L; - } - - for (i = length(a) - 1; i >= 1; i--) { - if (a[i] < b[i]) { - if (sign(a) == POS) { - return -1L; - } - else { - return 1L; - } - } - if (a[i] > b[i]) { - if (sign(a) == NEG) { - return -1L; - } - else { - return 1L; - } - } - } - return 0L; -} - -long mp_greater(lrs_mp a, lrs_mp b) /* tests if a > b and returns (TRUE=POS) */ -{ - long i; - - if (a[0] > b[0]) { - return (TRUE); - } - if (a[0] < b[0]) { - return (FALSE); - } - - for (i = length(a) - 1; i >= 1; i--) { - if (a[i] < b[i]) { - if (sign(a) == POS) { - return (0); - } - else { - return (1); - } - } - if (a[i] > b[i]) { - if (sign(a) == NEG) { - return (0); - } - else { - return (1); - } - } - } - return (0); -} -void itomp(long in, lrs_mp a) -/* convert integer i to multiple precision with base BASE */ -{ - long i; - a[0] = 2; /* initialize to zero */ - for (i = 1; i < lrs_digits; i++) { - a[i] = 0; - } - if (in < 0) { - storesign(a, NEG); - in = in * (-1); - } - i = 0; - while (in != 0) { - i++; - a[i] = in - BASE * (in / BASE); - in = in / BASE; - storelength(a, i + 1); - } -} /* end of itomp */ - -void linint(lrs_mp a, long ka, lrs_mp b, long kb) /*compute a*ka+b*kb --> a */ -/***Handbook of Algorithms and Data Structures P.239 ***/ -{ - long i, la, lb; - la = length(a); - lb = length(b); - for (i = 1; i < la; i++) { - a[i] *= ka; - } - if (sign(a) != sign(b)) { - kb = (-kb); - } - if (lb > la) { - storelength(a, lb); - for (i = la; i < lb; i++) { - a[i] = 0; - } - } - for (i = 1; i < lb; i++) { - a[i] += kb * b[i]; - } - normalize(a); -} -/***end of linint***/ - -void mptodouble(lrs_mp a, double *x) /* convert lrs_mp to double */ -{ - long i, la; - double y = 1.0; - - (*x) = 0; - la = length(a); - for (i = 1; i < la; i++) { - (*x) = (*x) + y * a[i]; - y = y * BASE; - } - if (negative(a)) { - (*x) = -(*x); - } -} - -void mulint(lrs_mp a, lrs_mp b, lrs_mp c) /* multiply two integers a*b --> c */ - -/***Handbook of Algorithms and Data Structures, p239 ***/ -{ - long nlength, i, j, la, lb; - /*** b and c may coincide ***/ - la = length(a); - lb = length(b); - nlength = la + lb - 2; - if (nlength > lrs_digits) { - digits_overflow(); - } - - for (i = 0; i < la - 2; i++) { - c[lb + i] = 0; - } - for (i = lb - 1; i > 0; i--) { - for (j = 2; j < la; j++) { - if ((c[i + j - 1] += b[i] * a[j]) > MAXD - (BASE - 1) * (BASE - 1) - MAXD / BASE) { - c[i + j - 1] -= (MAXD / BASE) * BASE; - c[i + j] += MAXD / BASE; - } - } - c[i] = b[i] * a[1]; - } - storelength(c, nlength); - storesign(c, sign(a) == sign(b) ? POS : NEG); - normalize(c); -} -/***end of mulint ***/ - -void normalize(lrs_mp a) -{ - long cy, i, la; - la = length(a); -start: - cy = 0; - for (i = 1; i < la; i++) { - cy = (a[i] += cy) / BASE; - a[i] -= cy * BASE; - if (a[i] < 0) { - a[i] += BASE; - cy--; - } - } - while (cy > 0) { - a[i++] = cy % BASE; - cy /= BASE; - } - if (cy < 0) { - a[la - 1] += cy * BASE; - for (i = 1; i < la; i++) { - a[i] = (-a[i]); - } - storesign(a, sign(a) == POS ? NEG : POS); - goto start; - } - while (a[i - 1] == 0 && i > 2) { - i--; - } - if (i > lrs_record_digits) { - if ((lrs_record_digits = i) > lrs_digits) { - digits_overflow(); - } - }; - storelength(a, i); - if (i == 2 && a[1] == 0) { - storesign(a, POS); - } -} /* end of normalize */ - -long length(lrs_mp a) -{ - /* formerly a macro but conflicts with string length */ - return ((a[0] > 0) ? a[0] : -a[0]); -} - -long mptoi(lrs_mp a) /* convert lrs_mp to long integer */ -{ - long len = length(a); - if (len == 2) { - return sign(a) * a[1]; - } - if (len == 3) { - return sign(a) * (a[1] + BASE * a[2]); - } - notimpl("mp to large for conversion to long"); - return 0; /* never executed */ -} - -#ifdef PLRS -string prat(const char name[], lrs_mp Nin, lrs_mp Din) /*reduce and print Nin/Din */ -{ - - lrs_mp Nt, Dt; - long i; - // create stream to collect output - stringstream ss; - string str; - - ss << name; - - /* reduce fraction */ - copy(Nt, Nin); - copy(Dt, Din); - reduce(Nt, Dt); - /* pipe output to stream */ - if (sign(Nin) * sign(Din) == NEG) { - ss << "-"; - } - else { - ss << " "; - } - - ss << Nt[length(Nt) - 1]; - - for (i = length(Nt) - 2; i >= 1; i--) { - ss << Nt[i]; - } - if (!(Dt[0] == 2 && Dt[1] == 1)) { - /* rational */ - ss << "/"; - ss << Dt[length(Dt) - 1]; - for (i = length(Dt) - 2; i >= 1; i--) { - ss << Dt[i]; - } - } - ss << " "; - // pipe stream to single string - str = ss.str(); - return str; -} - -char *cprat(const char name[], lrs_mp Nin, lrs_mp Din) -{ - char *ret; - unsigned long len; - int i, offset = 0; - string s; - const char *cstr; - - s = prat(name, Nin, Din); - cstr = s.c_str(); - len = strlen(cstr); - ret = (char *)malloc(sizeof(char) * (len + 1)); - - for (i = 0; i + offset < len + 1;) { - if (cstr[i + offset] != ' ') { - ret[i] = cstr[i + offset]; - i++; - } - else { /* skip whitespace */ - offset++; - } - } - - return ret; -} - -string pmp(char name[], lrs_mp a) /*print the long precision integer a */ -{ - - long i; - // create stream to collect output - stringstream ss; - string str; - - ss << name; - if (sign(a) == NEG) { - ss << "-"; - } - else { - ss << " "; - } - - ss << a[length(a) - 1]; - for (i = length(a) - 2; i >= 1; i--) { - ss << a[i]; - } - - ss << " "; - - // pipe stream to single string - str = ss.str(); - return str; -} -#else -void prat(const char name[], lrs_mp Nin, lrs_mp Din) /*reduce and print Nin/Din */ -{ - lrs_mp Nt, Dt; - long i; - fprintf(lrs_ofp, "%s", name); - /* reduce fraction */ - copy(Nt, Nin); - copy(Dt, Din); - reduce(Nt, Dt); - /* print out */ - if (sign(Nin) * sign(Din) == NEG) { - fprintf(lrs_ofp, "-"); - } - else { - fprintf(lrs_ofp, " "); - } - fprintf(lrs_ofp, "%lu", Nt[length(Nt) - 1]); - for (i = length(Nt) - 2; i >= 1; i--) { - fprintf(lrs_ofp, FORMAT, Nt[i]); - } - if (!(Dt[0] == 2 && Dt[1] == 1)) /* rational */ - { - fprintf(lrs_ofp, "/"); - fprintf(lrs_ofp, "%lu", Dt[length(Dt) - 1]); - for (i = length(Dt) - 2; i >= 1; i--) { - fprintf(lrs_ofp, FORMAT, Dt[i]); - } - } - fprintf(lrs_ofp, " "); -} - -void pmp(char name[], lrs_mp a) /*print the long precision integer a */ -{ - - long i; - fprintf(lrs_ofp, "%s", name); - if (sign(a) == NEG) { - fprintf(lrs_ofp, "-"); - } - else { - fprintf(lrs_ofp, " "); - } - fprintf(lrs_ofp, "%lu", a[length(a) - 1]); - for (i = length(a) - 2; i >= 1; i--) { - fprintf(lrs_ofp, FORMAT, a[i]); - } - fprintf(lrs_ofp, " "); -} -#endif - -long readrat(lrs_mp Na, lrs_mp Da) -/* read a rational or integer and convert to lrs_mp with base BASE */ -/* returns true if denominator is not one */ -/* returns 999 if premature end of file */ -{ - char in[MAXINPUT], num[MAXINPUT], den[MAXINPUT]; - if (fscanf(lrs_ifp, "%s", in) == EOF) { - fprintf(lrs_ofp, "\nInvalid input: check you have entered enough data!\n"); - exit(1); - } - if (!strcmp(in, "end")) /*premature end of input file */ - { - return (999L); - } - atoaa(in, num, den); /*convert rational to num/dem strings */ - atomp(num, Na); - if (den[0] == '\0') { - itomp(1L, Da); - return (FALSE); - } - atomp(den, Da); - return (TRUE); -} - -void addint(lrs_mp a, lrs_mp b, lrs_mp c) /* compute c=a+b */ -{ - copy(c, a); - linint(c, 1, b, 1); -} - -void atomp(char s[], lrs_mp a) /*convert string to lrs_mp integer */ -{ - lrs_mp mpone; - long diff, ten, i, sig; - itomp(1L, mpone); - ten = 10L; - for (i = 0; s[i] == ' ' || s[i] == '\n' || s[i] == '\t'; i++) - ; - /*skip white space */ - sig = POS; - if (s[i] == '+' || s[i] == '-') { /* sign */ - sig = (s[i++] == '+') ? POS : NEG; - } - itomp(0L, a); - while (s[i] >= '0' && s[i] <= '9') { - diff = s[i] - '0'; - linint(a, ten, mpone, diff); - i++; - } - storesign(a, sig); - if (s[i]) { - fprintf(stderr, "\nIllegal character in number: '%s'\n", s + i); - exit(1); - } - -} /* end of atomp */ - -void subint(lrs_mp a, lrs_mp b, lrs_mp c) /* compute c=a-b */ - -{ - copy(c, a); - linint(a, 1, b, -1); -} - -void decint(lrs_mp a, lrs_mp b) /* compute a=a-b */ { linint(a, 1, b, -1); } - -long myrandom(long num, long nrange) -/* return a random number in range 0..nrange-1 */ - -{ - long i; - i = (num * 401 + 673) % nrange; - return (i); -} - -long atos(char s[]) /* convert s to integer */ -{ - long i, j; - j = 0; - for (i = 0; s[i] >= '0' && s[i] <= '9'; ++i) { - j = 10 * j + s[i] - '0'; - } - return (j); -} - -void stringcpy(char *s, char *t) /*copy t to s pointer version */ -{ - while (((*s++) = (*t++)) != '\0') - ; -} - -void rattodouble(lrs_mp a, lrs_mp b, double *x) /* convert lrs_mp rational to double */ - -{ - double y; - mptodouble(a, &y); - mptodouble(b, x); - *x = y / (*x); -} - -void atoaa(char in[], char num[], char den[]) -/* convert rational string in to num/den strings */ -{ - long i, j; - for (i = 0; in[i] != '\0' && in[i] != '/'; i++) { - num[i] = in[i]; - } - num[i] = '\0'; - den[0] = '\0'; - if (in[i] == '/') { - for (j = 0; in[j + i + 1] != '\0'; j++) { - den[j] = in[i + j + 1]; - } - den[j] = '\0'; - } -} /* end of atoaa */ - -void lcm(lrs_mp a, lrs_mp b) -/* a = least common multiple of a, b; b is preserved */ -{ - lrs_mp u, v; - copy(u, a); - copy(v, b); - gcd(u, v); - exactdivint(a, u, v); /* v=a/u no remainder*/ - mulint(v, b, a); -} /* end of lcm */ - -void reducearray(lrs_mp_vector p, long n) -/* find largest gcd of p[0]..p[n-1] and divide through */ -{ - lrs_mp divisor; - lrs_mp Temp; - long i = 0L; - - while ((i < n) && zero(p[i])) { - i++; - } - if (i == n) { - return; - } - - copy(divisor, p[i]); - storesign(divisor, POS); - i++; - - while (i < n) { - if (!zero(p[i])) { - copy(Temp, p[i]); - storesign(Temp, POS); - gcd(divisor, Temp); - } - i++; - } - - /* reduce by divisor */ - for (i = 0; i < n; i++) { - if (!zero(p[i])) { - reduceint(p[i], divisor); - } - } -} /* end of reducearray */ - -void reduceint(lrs_mp Na, lrs_mp Da) /* divide Na by Da and return */ -{ - lrs_mp Temp; - copy(Temp, Na); - exactdivint(Temp, Da, Na); -} - -void reduce(lrs_mp Na, lrs_mp Da) /* reduces Na Da by gcd(Na,Da) */ -{ - lrs_mp Nb, Db, Nc, Dc; - copy(Nb, Na); - copy(Db, Da); - storesign(Nb, POS); - storesign(Db, POS); - copy(Nc, Na); - copy(Dc, Da); - gcd(Nb, Db); /* Nb is the gcd(Na,Da) */ - exactdivint(Nc, Nb, Na); - exactdivint(Dc, Nb, Da); -} - -long comprod(lrs_mp Na, lrs_mp Nb, lrs_mp Nc, lrs_mp Nd) /* +1 if Na*Nb > Nc*Nd */ - /* -1 if Na*Nb < Nc*Nd */ - /* 0 if Na*Nb = Nc*Nd */ -{ - lrs_mp mc, md; - mulint(Na, Nb, mc); - mulint(Nc, Nd, md); - linint(mc, ONE, md, -ONE); - if (positive(mc)) { - return (1); - } - if (negative(mc)) { - return (-1); - } - return (0); -} - -void notimpl(char s[]) -{ - fflush(stdout); - fprintf(stderr, "\nAbnormal Termination %s\n", s); - exit(1); -} - -void getfactorial(lrs_mp factorial, long k) /* compute k factorial in lrs_mp */ -{ - lrs_mp temp; - long i; - itomp(ONE, factorial); - for (i = 2; i <= k; i++) { - itomp(i, temp); - mulint(temp, factorial, factorial); - } -} /* end of getfactorial */ -/***************************************************************/ -/* Package of routines for rational arithmetic */ -/***************************************************************/ - -void scalerat(lrs_mp Na, lrs_mp Da, long ka) /* scales rational by ka */ -{ - lrs_mp Nt; - copy(Nt, Na); - itomp(ZERO, Na); - linint(Na, ZERO, Nt, ka); - reduce(Na, Da); -} - -void linrat(lrs_mp Na, lrs_mp Da, long ka, lrs_mp Nb, lrs_mp Db, long kb, lrs_mp Nc, lrs_mp Dc) -/* computes Nc/Dc = ka*Na/Da +kb* Nb/Db - and reduces answer by gcd(Nc,Dc) */ -{ - lrs_mp c; - mulint(Na, Db, Nc); - mulint(Da, Nb, c); - linint(Nc, ka, c, kb); /* Nc = (ka*Na*Db)+(kb*Da*Nb) */ - mulint(Da, Db, Dc); /* Dc = Da*Db */ - reduce(Nc, Dc); -} - -void divrat(lrs_mp Na, lrs_mp Da, lrs_mp Nb, lrs_mp Db, lrs_mp Nc, lrs_mp Dc) -/* computes Nc/Dc = (Na/Da) / ( Nb/Db ) - and reduces answer by gcd(Nc,Dc) */ -{ - mulint(Na, Db, Nc); - mulint(Da, Nb, Dc); - reduce(Nc, Dc); -} - -void mulrat(lrs_mp Na, lrs_mp Da, lrs_mp Nb, lrs_mp Db, lrs_mp Nc, lrs_mp Dc) -/* computes Nc/Dc = Na/Da * Nb/Db and reduces by gcd(Nc,Dc) */ -{ - mulint(Na, Nb, Nc); - mulint(Da, Db, Dc); - reduce(Nc, Dc); -} - -/* End package of routines for rational arithmetic */ - -/***************************************************************/ -/* */ -/* End of package for multiple precision arithmetic */ -/* */ -/***************************************************************/ - -void *xcalloc(long n, long s, long l, char *f) -{ - void *tmp; - - tmp = calloc(n, s); - if (tmp == 0) { - char buf[200]; - - sprintf(buf, "\n\nFatal error on line %ld of %s", l, f); - perror(buf); - exit(1); - } - return tmp; -} - -void lrs_getdigits(long *a, long *b) -{ - /* send digit information to user */ - *a = DIG2DEC(lrs_digits); - *b = DIG2DEC(lrs_record_digits); - return; -} - -void lrs_default_digits_overflow() -{ - fprintf(stdout, "\nOverflow at digits=%ld", DIG2DEC(lrs_digits)); - fprintf(stdout, "\nInitialize lrs_mp_init with n > %ldL\n", DIG2DEC(lrs_digits)); - - exit(1); -} - -#ifdef PLRS - -/* read a rational or integer and convert to lrs_mp with base BASE */ -/* returns true if denominator is not one */ -/* returns 999 if premature end of file */ -long plrs_readrat(lrs_mp Na, lrs_mp Da, const char *rat) -{ - char in[MAXINPUT], num[MAXINPUT], den[MAXINPUT]; - strcpy(in, rat); - atoaa(in, num, den); /*convert rational to num/dem strings */ - atomp(num, Na); - if (den[0] == '\0') { - itomp(1L, Da); - return (FALSE); - } - atomp(den, Da); - return (TRUE); -} - -#endif - -/* end of lrsmp.c */ diff --git a/src/solvers/lrs/lrsmp.h b/src/solvers/lrs/lrsmp.h deleted file mode 100644 index e5a3c2509..000000000 --- a/src/solvers/lrs/lrsmp.h +++ /dev/null @@ -1,232 +0,0 @@ -/* lrsmp.h (lrs extended precision arithmetic library) */ -/* Copyright: David Avis 2000, avis@cs.mcgill.ca */ -/* Version 4.1, February 17, 2000 */ - -/* 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., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. - */ -/******************************************************************************/ -/* See http://cgm.cs.mcgill.ca/~avis/C/lrs.html for lrs usage instructions */ -/******************************************************************************/ -/* This package contains the extended precision routines used by lrs - and some other miscellaneous routines. The maximum precision depends on - the parameter MAX_DIGITS defined below, with usual default of 255L. This - gives a maximum of 1020 decimal digits on 32 bit machines. The procedure - lrs_mp_init(dec_digits) may set a smaller number of dec_digits, and this - is useful if arrays or matrices will be used. - */ - -#ifdef PLRS -#include -using namespace std; -#endif - -/***********/ -/* defines */ -/***********/ -/* - this is number of longwords. Increasing this won't cost you that much - since only variables other than the A matrix are allocated this size. - Changing affects running time in small but not very predictable ways. - */ - -#define MAX_DIGITS 255L - -/* - this is in decimal digits, you pay in memory if you increase this, - unless you override by a line with - digits n - before the begin line of your file. - */ -#define DEFAULT_DIGITS 100L - -/**********MACHINE DEPENDENT CONSTANTS***********/ -/* MAXD is 2^(k-1)-1 where k=16,32,64 word size */ -/* MAXD must be at least 2*BASE^2 */ -/* If BASE is 10^k, use "%k.ku" for FORMAT */ -/* INTSIZE is number of bytes for integer */ -/* 32/64 bit machines */ -/***********************************************/ -#ifdef B32 -/*32 bit machines */ -#define FORMAT "%4.4lu" -#define MAXD 2147483647L -#define BASE 10000L -#define BASE_DIG 4 -#define INTSIZE 8L -#define BIT "32bit" -#else -/* 64 bit machines */ -#define MAXD 9223372036854775807L -#define BASE 1000000000L -#define FORMAT "%9.9lu" -#define BASE_DIG 9 -#define INTSIZE 16L -#define BIT "64bit" -#endif - -#define MAXINPUT 1000 /*max length of any input rational */ - -#define POS 1L -#define NEG -1L -#ifndef TRUE -#define TRUE 1L -#endif -#ifndef FALSE -#define FALSE 0L -#endif -#define ONE 1L -#define TWO 2L -#define ZERO 0L - -/**********************************/ -/* MACROS */ -/* dependent on mp implementation */ -/**********************************/ - -#define exactdivint(a, b, c) divint((a), (b), (c)) /*should use special code here */ -#define positive(a) (((a)[0] < 2 || ((a)[0] == 2 && (a)[1] == 0)) ? FALSE : TRUE) -#define negative(a) (((a)[0] > -2 || ((a)[0] == -2 && (a)[1] == 0)) ? FALSE : TRUE) -#define zero(a) ((((a)[0] == 2 || (a)[0] == -2) && (a)[1] == 0) ? TRUE : FALSE) -#define one(a) (((a)[0] == 2 && (a)[1] == 1) ? TRUE : FALSE) -// #define length(a) (((a)[0] > 0) ? (a)[0] : -(a)[0]) -#define sign(a) (((a)[0] < 0) ? NEG : POS) -#define storesign(a, sa) a[0] = ((a)[0] > 0) ? (sa) * ((a)[0]) : -(sa) * ((a)[0]) -#define changesign(a) a[0] = -(a)[0] -#define storelength(a, la) a[0] = ((a)[0] > 0) ? (la) : -(la) - -/* - * convert between decimal and machine (longword digits). Notice lovely - * implementation of ceiling function :-) - */ -#define DEC2DIG(d) ((d) % BASE_DIG ? (d) / BASE_DIG + 1 : (d) / BASE_DIG) -#define DIG2DEC(d) ((d) * BASE_DIG) - -#include - -#ifdef SIGNALS -#include -#include -#define errcheck(s, e) \ - if ((long)(e) == -1L) { \ - perror(s); \ - exit(1); \ - } -#endif - -#define CALLOC(n, s) xcalloc(n, s, __LINE__, __FILE__) - -extern long lrs_digits; /* max permitted no. of digits */ -extern long lrs_record_digits; /* this is the biggest acheived so far. */ - -extern FILE *lrs_ifp; /* input file pointer */ -extern FILE *lrs_ofp; /* output file pointer */ - -/*************/ -/* typedefs */ -/*************/ - -typedef long lrs_mp[MAX_DIGITS + 1]; /* type lrs_mp holds one multi-precision integer */ -typedef long *lrs_mp_t; -typedef long **lrs_mp_vector; -typedef long ***lrs_mp_matrix; - -/*********************************************************/ -/* Initialization and allocation procedures - must use! */ -/******************************************************* */ - -/* next two functions are not used by lrsmp, but are for lrsgmp compatability */ -#define lrs_alloc_mp(a) -#define lrs_clear_mp(a) -lrs_mp_t lrs_alloc_mp_t(); /* dynamic allocation of lrs_mp */ -lrs_mp_vector lrs_alloc_mp_vector(long n); /* allocate lrs_mp_vector for n+1 lrs_mp numbers */ -lrs_mp_matrix lrs_alloc_mp_matrix(long m, - long n); /* allocate lrs_mp_matrix for m+1 x n+1 lrs_mp */ -long lrs_mp_init(long dec_digits, FILE *lrs_ifp, - FILE *lrs_ofp); /* max number of decimal digits, fps */ -void lrs_clear_mp_vector(lrs_mp_vector a, long n); -void lrs_clear_mp_matrix(lrs_mp_matrix a, long m, long n); - -/*********************************************************/ -/* Core library functions - depend on mp implementation */ -/******************************************************* */ -long length(lrs_mp a); /* return length of lrs_mp integer */ -void atomp(char s[], lrs_mp a); /* convert string to lrs_mp integer */ -long compare(lrs_mp a, lrs_mp b); /* a ? b and returns -1,0,1 for <,=,> */ -void copy(lrs_mp a, lrs_mp b); /* assigns a=b */ -void divint(lrs_mp a, lrs_mp b, lrs_mp c); /* c=a/b, a contains remainder on return */ -void gcd(lrs_mp u, lrs_mp v); /* returns u=gcd(u,v) destroying v */ -long mp_greater(lrs_mp a, lrs_mp b); /* tests if a > b and returns (TRUE=POS) */ -void itomp(long in, lrs_mp a); /* convert integer i to lrs_mp */ -void linint(lrs_mp a, long ka, lrs_mp b, long kb); /* compute a*ka+b*kb --> a */ -void mptodouble(lrs_mp a, double *x); /* convert lrs_mp to double */ -long mptoi(lrs_mp a); /* convert lrs_mp to long integer */ -void mulint(lrs_mp a, lrs_mp b, lrs_mp c); /* multiply two integers a*b --> c */ -void normalize(lrs_mp a); /* normalize lrs_mp after computation */ -#ifdef PLRS -string pmp(char name[], lrs_mp a); /* print the long precision integer a */ -string prat(const char name[], lrs_mp Nt, lrs_mp Dt); /* reduce and print Nt/Dt */ -char *cprat(const char name[], lrs_mp Nt, lrs_mp Dt); /* C version of prat */ -long plrs_readrat(lrs_mp Na, lrs_mp Da, - const char *rat); /* take a rational number and convert to lrs_mp */ -#else -void pmp(char name[], lrs_mp a); /* print the long precision integer a */ -void prat(const char name[], lrs_mp Nt, lrs_mp Dt); /* reduce and print Nt/Dt */ -#endif -long readrat(lrs_mp Na, lrs_mp Da); /* read a rational or int and convert to lrs_mp */ -void reduce(lrs_mp Na, lrs_mp Da); /* reduces Na Da by gcd(Na,Da) */ - -/*********************************************************/ -/* Standard arithmetic & misc. functions */ -/* should be independent of mp implementation */ -/******************************************************* */ - -void atoaa(char in[], char num[], char den[]); /* convert rational string in to num/den strings */ -void addint(lrs_mp a, lrs_mp b, lrs_mp c); /* compute c=a+b */ -long atos(char s[]); /* convert s to integer */ -long comprod(lrs_mp Na, lrs_mp Nb, lrs_mp Nc, - lrs_mp Nd); /* +1 if Na*Nb > Nc*Nd,-1 if Na*Nb > Nc*Nd else 0 */ -void decint(lrs_mp a, lrs_mp b); /* compute a=a-b */ -void divrat(lrs_mp Na, lrs_mp Da, lrs_mp Nb, lrs_mp Db, lrs_mp Nc, lrs_mp Dc); -/* computes Nc/Dc = (Na/Da) /( Nb/Db ) and reduce */ -void getfactorial(lrs_mp factorial, long k); /* compute k factorial in lrs_mp */ - /* NC/DC = ka*Na/Da + kb*Nb/Db */ -void linrat(lrs_mp Na, lrs_mp Da, long ka, lrs_mp Nb, lrs_mp Db, long kb, lrs_mp Nc, lrs_mp Dc); -void lcm(lrs_mp a, lrs_mp b); /* a = least common multiple of a, b; b is saved */ -void mulrat(lrs_mp Na, lrs_mp Da, lrs_mp Nb, lrs_mp Db, lrs_mp Nc, lrs_mp Dc); -/* computes Nc/Dc=(Na/Da)*(Nb/Db) and reduce */ -long myrandom(long num, long nrange); /* return a random number in range 0..nrange-1 */ -void notimpl(char s[]); /* bail out - help! */ -void rattodouble(lrs_mp a, lrs_mp b, double *x); /* convert lrs_mp rational to double */ -void reduceint(lrs_mp Na, lrs_mp Da); /* divide Na by Da and return it */ -void reducearray(lrs_mp_vector p, long n); /* find gcd of p[0]..p[n-1] and divide through by */ -void scalerat(lrs_mp Na, lrs_mp Da, long ka); /* scales rational by ka */ -void subint(lrs_mp a, lrs_mp b, lrs_mp c); /* compute c=a-b */ - -/**********************************/ -/* Miscellaneous functions */ -/******************************** */ - -void free(void *); - -void lrs_getdigits(long *a, long *b); /* send digit information to user */ - -void stringcpy(char *s, char *t); /* copy t to s pointer version */ - -void *xcalloc(long n, long s, long l, char *f); - -void lrs_default_digits_overflow(); -void digits_overflow(); - -/* end of lrsmp.h (vertex enumeration using lexicographic reverse search) */ diff --git a/src/solvers/lrs/lrsnashlib.c b/src/solvers/lrs/lrsnashlib.c deleted file mode 100644 index 44acf4e25..000000000 --- a/src/solvers/lrs/lrsnashlib.c +++ /dev/null @@ -1,1195 +0,0 @@ -/*******************************************************/ -/* lrsnashlib is a library of routines for computing */ -/* computing all nash equilibria for two person games */ -/* given by mxn payoff matrices A,B */ -/* */ -/* */ -/* Main user callable function is */ -/* lrs_solve_nash(game *g) */ -/* */ -/* Requires lrsnashlib.h lrslib.h lrslib.c */ -/* */ -/* Sample driver: lrsnash.c */ -/* Derived from nash.c in lrslib-060 */ -/* by Terje Lensberg, October 26, 2015: */ -/*******************************************************/ - -#include -#include -#include "lrslib.h" -#include "lrsnashlib.h" - -//======================================================================== -// Standard solver. Modified version of main() from lrsNash -//======================================================================== -int lrs_solve_nash(game *g) -{ - lrs_dic *P1 /*, *P2*/; /* structure for holding current dictionary and indices */ - lrs_dat *Q1, *Q2; /* structure for holding static problem data */ - - lrs_mp_vector output1; /* holds one line of output; ray,vertex,facet,linearity */ - lrs_mp_vector output2; /* holds one line of output; ray,vertex,facet,linearity */ - lrs_mp_matrix Lin; /* holds input linearities if any are found */ - lrs_mp_matrix A2orig; - lrs_dic *P2orig; /* we will save player 2's dictionary in getabasis */ - - long *linindex; /* for faster restart of player 2 */ - - long col; /* output column index for dictionary */ - long startcol = 0; - long prune = FALSE; /* if TRUE, getnextbasis will prune tree and backtrack */ - long numequilib = 0; /* number of nash equilibria found */ - long oldnum = 0; - - /* global variables lrs_ifp and lrs_ofp are file pointers for input and output */ - /* they default to stdin and stdout, but may be overidden by command line parms. */ - - /*********************************************************************************/ - /* Step 1: Allocate lrs_dat, lrs_dic and set up the problem */ - /*********************************************************************************/ - FirstTime = TRUE; /* This is done for each new game */ - - Q1 = lrs_alloc_dat("LRS globals"); /* allocate and init structure for static problem data */ - if (Q1 == NULL) { - return 1; - } - - Q1->nash = TRUE; - Q1->n = g->nstrats[ROW] + 2; - Q1->m = g->nstrats[ROW] + g->nstrats[COL] + 1; - - Q1->debug = Debug_flag; - Q1->verbose = Verbose_flag; - - P1 = lrs_alloc_dic(Q1); /* allocate and initialize lrs_dic */ - if (P1 == NULL) { - return 1; - } - - BuildRep(P1, Q1, g, 1, 0); - - output1 = - lrs_alloc_mp_vector(Q1->n + Q1->m); /* output holds one line of output from dictionary */ - - /* allocate and init structure for player 2's problem data */ - Q2 = lrs_alloc_dat("LRS globals"); - if (Q2 == NULL) { - return 1; - } - - Q2->debug = Debug_flag; - Q2->verbose = Verbose_flag; - - Q2->nash = TRUE; - Q2->n = g->nstrats[COL] + 2; - Q2->m = g->nstrats[ROW] + g->nstrats[COL] + 1; - - P2orig = lrs_alloc_dic(Q2); /* allocate and initialize lrs_dic */ - if (P2orig == NULL) { - return 1; - } - BuildRep(P2orig, Q2, g, 0, 1); - A2orig = P2orig->A; - - output2 = - lrs_alloc_mp_vector(Q1->n + Q1->m); /* output holds one line of output from dictionary */ - - linindex = calloc((P2orig->m + P2orig->d + 2), sizeof(long)); /* for next time */ - - fprintf(lrs_ofp, "\n"); - // fprintf (lrs_ofp, "***** %ld %ld rational\n", Q1->n, Q2->n); - - /*********************************************************************************/ - /* Step 2: Find a starting cobasis from default of specified order */ - /* P1 is created to hold active dictionary data and may be cached */ - /* Lin is created if necessary to hold linearity space */ - /* Print linearity space if any, and retrieve output from first dict. */ - /*********************************************************************************/ - - if (!lrs_getfirstbasis(&P1, Q1, &Lin, TRUE)) { - return 1; - } - - if (Q1->dualdeg) { - printf("\n*Warning! Dual degenerate, ouput may be incomplete"); - printf("\n*Recommendation: Add dualperturb option before maximize in first input file\n"); - } - - if (Q1->unbounded) { - printf("\n*Warning! Unbounded starting dictionary for p1, output may be incomplete"); - printf("\n*Recommendation: Change/remove maximize option, or include bounds \n"); - } - - /* Pivot to a starting dictionary */ - /* There may have been column redundancy */ - /* If so the linearity space is obtained and redundant */ - /* columns are removed. User can access linearity space */ - /* from lrs_mp_matrix Lin dimensions nredundcol x d+1 */ - - if (Q1->homogeneous && Q1->hull) { - startcol++; /* col zero not treated as redundant */ - } - - for (col = startcol; col < Q1->nredundcol; col++) { /* print linearity space */ - lrs_printoutput(Q1, Lin[col]); /* Array Lin[][] holds the coeffs. */ - } - - /*********************************************************************************/ - /* Step 3: Terminate if lponly option set, otherwise initiate a reverse */ - /* search from the starting dictionary. Get output for each new dict. */ - /*********************************************************************************/ - - /* We initiate reverse search from this dictionary */ - /* getting new dictionaries until the search is complete */ - /* User can access each output line from output which is */ - /* vertex/ray/facet from the lrs_mp_vector output */ - /* prune is TRUE if tree should be pruned at current node */ - do { - prune = lrs_checkbound(P1, Q1); - if (!prune && lrs_getsolution(P1, Q1, output1, col)) { - oldnum = numequilib; - nash2_main(P1, Q1, P2orig, Q2, &numequilib, output2, linindex); - if (numequilib > oldnum || Q1->verbose) { - if (Q1->verbose) { - prat(" \np2's obj value: ", P1->objnum, P1->objden); - } - lrs_nashoutput(Q1, output1, 1L); - fprintf(lrs_ofp, "\n"); - } - } - } while (lrs_getnextbasis(&P1, Q1, prune)); - - fprintf(lrs_ofp, "*Number of equilibria found: %ld", numequilib); - fprintf(lrs_ofp, "\n*Player 1: vertices=%ld bases=%ld pivots=%ld", Q1->count[1], Q1->count[2], - Q1->count[3]); - fprintf(lrs_ofp, "\n*Player 2: vertices=%ld bases=%ld pivots=%ld", Q2->count[1], Q2->count[2], - Q2->count[3]); - - lrs_clear_mp_vector(output1, Q1->m + Q1->n); - lrs_clear_mp_vector(output2, Q1->m + Q1->n); - - lrs_free_dic(P1, Q1); /* deallocate lrs_dic */ - lrs_free_dat(Q1); /* deallocate lrs_dat */ - - /* 2015.10.10 new code to clear P2orig */ - Q2->Qhead = P2orig; /* reset this or you crash free_dic */ - P2orig->A = A2orig; /* reset this or you crash free_dic */ - - lrs_free_dic(P2orig, Q2); /* deallocate lrs_dic */ - lrs_free_dat(Q2); /* deallocate lrs_dat */ - - free(linindex); - - // lrs_close("nash:"); - fprintf(lrs_ofp, "\n"); - return 0; -} - -/*********************************************/ -/* end of nash driver */ -/*********************************************/ - -/**********************************************************/ -/* nash2_main is a second driver used in computing nash */ -/* equilibria on a second polytope interleaved with first */ -/**********************************************************/ - -long nash2_main(lrs_dic *P1, lrs_dat *Q1, lrs_dic *P2orig, lrs_dat *Q2, long *numequilib, - lrs_mp_vector output, long linindex[]) -{ - - lrs_dic *P2; /* This can get resized, cached etc. Loaded from P2orig */ - lrs_mp_matrix Lin; /* holds input linearities if any are found */ - long col; /* output column index for dictionary */ - long startcol = 0; - long prune = FALSE; /* if TRUE, getnextbasis will prune tree and backtrack */ - long nlinearity; - long *linearity; - static long firstwarning = TRUE; /* FALSE if dual deg warning for Q2 already given */ - static long firstunbounded = TRUE; /* FALSE if dual deg warning for Q2 already given */ - - long i, j; - - /* global variables lrs_ifp and lrs_ofp are file pointers for input and output */ - /* they default to stdin and stdout, but may be overidden by command line parms. */ - - /*********************************************************************************/ - /* Step 1: Allocate lrs_dat, lrs_dic and set up the problem */ - /*********************************************************************************/ - - P2 = lrs_getdic(Q2); - copy_dict(Q2, P2, P2orig); - - /* Here we take the linearities generated by the current vertex of player 1*/ - /* and append them to the linearity in player 2's input matrix */ - /* next is the key magic linking player 1 and 2 */ - /* be careful if you mess with this! */ - - linearity = Q2->linearity; - nlinearity = 0; - for (i = Q1->lastdv + 1; i <= P1->m; i++) { - if (!zero(P1->A[P1->Row[i]][0])) { - j = Q1->inequality[P1->B[i] - Q1->lastdv]; - if (Q1->nlinearity == 0 || j < Q1->linearity[0]) { - linearity[nlinearity++] = j; - } - } - } - /* add back in the linearity for probs summing to one */ - if (Q1->nlinearity > 0) { - linearity[nlinearity++] = Q1->linearity[0]; - } - - /*sort linearities */ - for (i = 1; i < nlinearity; i++) { - reorder(linearity, nlinearity); - } - - if (Q2->verbose) { - fprintf(lrs_ofp, "\np2: linearities %ld", nlinearity); - for (i = 0; i < nlinearity; i++) { - fprintf(lrs_ofp, " %ld", linearity[i]); - } - } - - Q2->nlinearity = nlinearity; - Q2->polytope = FALSE; - - /*********************************************************************************/ - /* Step 2: Find a starting cobasis from default of specified order */ - /* P2 is created to hold active dictionary data and may be cached */ - /* Lin is created if necessary to hold linearity space */ - /* Print linearity space if any, and retrieve output from first dict. */ - /*********************************************************************************/ - - if (!lrs_getfirstbasis2(&P2, Q2, P2orig, &Lin, TRUE, linindex)) { - goto sayonara; - } - if (firstwarning && Q2->dualdeg) { - firstwarning = FALSE; - printf("\n*Warning! Dual degenerate, ouput may be incomplete"); - printf("\n*Recommendation: Add dualperturb option before maximize in second input file\n"); - } - if (firstunbounded && Q2->unbounded) { - firstunbounded = FALSE; - printf("\n*Warning! Unbounded starting dictionary for p2, output may be incomplete"); - printf("\n*Recommendation: Change/remove maximize option, or include bounds \n"); - } - - /* Pivot to a starting dictionary */ - /* There may have been column redundancy */ - /* If so the linearity space is obtained and redundant */ - /* columns are removed. User can access linearity space */ - /* from lrs_mp_matrix Lin dimensions nredundcol x d+1 */ - - if (Q2->homogeneous && Q2->hull) { - startcol++; /* col zero not treated as redundant */ - } - - /* for (col = startcol; col < Q2->nredundcol; col++) */ /* print linearity space */ - /*lrs_printoutput (Q2, Lin[col]); */ /* Array Lin[][] holds the coeffs. */ - - /*********************************************************************************/ - /* Step 3: Terminate if lponly option set, otherwise initiate a reverse */ - /* search from the starting dictionary. Get output for each new dict. */ - /*********************************************************************************/ - - /* We initiate reverse search from this dictionary */ - /* getting new dictionaries until the search is complete */ - /* User can access each output line from output which is */ - /* vertex/ray/facet from the lrs_mp_vector output */ - /* prune is TRUE if tree should be pruned at current node */ - do { - prune = lrs_checkbound(P2, Q2); - col = 0; - if (!prune && lrs_getsolution(P2, Q2, output, col)) { - if (Q2->verbose) { - prat(" \np1's obj value: ", P2->objnum, P2->objden); - } - if (lrs_nashoutput(Q2, output, 2L)) { - (*numequilib)++; - } - } - } while (lrs_getnextbasis(&P2, Q2, prune)); - -sayonara: - lrs_free_dic(P2, Q2); - return 0; -} - -/*********************************************/ -/* end of nash2_main */ -/*********************************************/ - -/* In lrs_getfirstbasis and lrs_getnextbasis we use D instead of P */ -/* since the dictionary P may change, ie. &P in calling routine */ - -#define D (*D_p) - -long lrs_getfirstbasis2(lrs_dic **D_p, lrs_dat *Q, lrs_dic *P2orig, lrs_mp_matrix *Lin, - long no_output, long linindex[]) -/* gets first basis, FALSE if none */ -/* P may get changed if lin. space Lin found */ -/* no_output is TRUE supresses output headers */ -{ - long i, j, k; - - /* assign local variables to structures */ - - lrs_mp_matrix A; - long *B, *C, /* *Row, */ *Col; - long *inequality; - long *linearity; - long hull = Q->hull; - long m, d, lastdv, nlinearity, nredundcol; - - // static long ocount = 0; - - m = D->m; - d = D->d; - lastdv = Q->lastdv; - - nredundcol = 0L; /* will be set after getabasis */ - nlinearity = Q->nlinearity; /* may be reset if new linearity read */ - linearity = Q->linearity; - - A = D->A; - B = D->B; - C = D->C; - // Row = D->Row; - Col = D->Col; - inequality = Q->inequality; - - /* default is to look for starting cobasis using linearies first, then */ - /* filling in from last rows of input as necessary */ - /* linearity array is assumed sorted here */ - /* note if restart/given start inequality indices already in place */ - /* from nlinearity..d-1 */ - - for (i = 0; i < nlinearity; i++) { /* put linearities first in the order */ - inequality[i] = linearity[i]; - } - - k = 0; /* index for linearity array */ - - if (Q->givenstart) { - k = d; - } - else { - k = nlinearity; - } - for (i = m; i >= 1; i--) { - j = 0; - while (j < k && inequality[j] != i) { - j++; /* see if i is in inequality */ - } - if (j == k) { - inequality[k++] = i; - } - } - if (Q->debug) { - fprintf(lrs_ofp, "\n*Starting cobasis uses input row order"); - for (i = 0; i < m; i++) { - fprintf(lrs_ofp, " %ld", inequality[i]); - } - } - - if (!Q->maximize && !Q->minimize) { - for (j = 0; j <= d; j++) { - itomp(ZERO, A[0][j]); - } - } - - /* Now we pivot to standard form, and then find a primal feasible basis */ - /* Note these steps MUST be done, even if restarting, in order to get */ - /* the same index/inequality correspondance we had for the original prob. */ - /* The inequality array is used to give the insertion order */ - /* and is defaulted to the last d rows when givenstart=FALSE */ - - if (!getabasis2(D, Q, P2orig, inequality, linindex)) { - return FALSE; - } - - if (Q->debug) { - fprintf(lrs_ofp, "\nafter getabasis2"); - printA(D, Q); - } - nredundcol = Q->nredundcol; - lastdv = Q->lastdv; - d = D->d; - - /********************************************************************/ - /* now we start printing the output file unless no output requested */ - /********************************************************************/ - if (!no_output || Q->debug) { - fprintf(lrs_ofp, "\nV-representation"); - - /* Print linearity space */ - /* Don't print linearity if first column zero in hull computation */ - - k = 0; - - if (nredundcol > k) { - fprintf(lrs_ofp, "\nlinearity %ld ", nredundcol - k); /*adjust nredundcol for homog. */ - for (i = 1; i <= nredundcol - k; i++) { - fprintf(lrs_ofp, " %ld", i); - } - } /* end print of linearity space */ - - fprintf(lrs_ofp, "\nbegin"); - fprintf(lrs_ofp, "\n***** %ld rational", Q->n); - - } /* end of if !no_output ....... */ - - /* Reset up the inequality array to remember which index is which input inequality */ - /* inequality[B[i]-lastdv] is row number of the inequality with index B[i] */ - /* inequality[C[i]-lastdv] is row number of the inequality with index C[i] */ - - for (i = 1; i <= m; i++) { - inequality[i] = i; - } - if (nlinearity > 0) { /* some cobasic indices will be removed */ - for (i = 0; i < nlinearity; i++) { /* remove input linearity indices */ - inequality[linearity[i]] = 0; - } - k = 1; /* counter for linearities */ - for (i = 1; i <= m - nlinearity; i++) { - while (k <= m && inequality[k] == 0) { - k++; /* skip zeroes in corr. to linearity */ - } - inequality[i] = inequality[k++]; - } - } /* end if linearity */ - if (Q->debug) { - fprintf(lrs_ofp, "\ninequality array initialization:"); - for (i = 1; i <= m - nlinearity; i++) { - fprintf(lrs_ofp, " %ld", inequality[i]); - } - } - if (nredundcol > 0) { - const unsigned int Qn = Q->n; - *Lin = lrs_alloc_mp_matrix(nredundcol, Qn); - - for (i = 0; i < nredundcol; i++) { - if (!(Q->homogeneous && Q->hull && i == 0)) { /* skip redund col 1 for homog. hull */ - lrs_getray(D, Q, Col[0], D->C[0] + i - hull, (*Lin)[i]); /* adjust index for deletions */ - } - - if (!removecobasicindex(D, Q, 0L)) { - lrs_clear_mp_matrix(*Lin, nredundcol, Qn); - return FALSE; - } - } - } /* end if nredundcol > 0 */ - - if (Q->verbose) { - fprintf(lrs_ofp, "\nNumber of pivots for starting dictionary: %ld", Q->count[3]); - // ocount = Q->count[3]; - } - - /* Do dual pivots to get primal feasibility */ - if (!primalfeasible(D, Q)) { - if (Q->verbose) { - fprintf(lrs_ofp, "\nNumber of pivots for feasible solution: %ld", Q->count[3]); - fprintf(lrs_ofp, " - No feasible solution"); - // ocount = Q->count[3]; - } - return FALSE; - } - - if (Q->verbose) { - fprintf(lrs_ofp, "\nNumber of pivots for feasible solution: %ld", Q->count[3]); - // ocount = Q->count[3]; - } - - /* Now solve LP if objective function was given */ - if (Q->maximize || Q->minimize) { - Q->unbounded = !lrs_solvelp(D, Q, Q->maximize); - - /* check to see if objective is dual degenerate */ - j = 1; - while (j <= d && !zero(A[0][j])) { - j++; - } - if (j <= d) { - Q->dualdeg = TRUE; - } - } - else - /* re-initialize cost row to -det */ - { - for (j = 1; j <= d; j++) { - copy(A[0][j], D->det); - storesign(A[0][j], NEG); - } - - itomp(ZERO, A[0][0]); /* zero optimum objective value */ - } - - /* reindex basis to 0..m if necessary */ - /* we use the fact that cobases are sorted by index value */ - if (Q->debug) { - printA(D, Q); - } - while (C[0] <= m) { - i = C[0]; - // j = inequality[B[i] - lastdv]; - // inequality[B[i] - lastdv] = inequality[C[0] - lastdv]; - // inequality[C[0] - lastdv] = j; - C[0] = B[i]; - B[i] = i; - reorder1(C, Col, ZERO, d); - } - - if (Q->debug) { - fprintf(lrs_ofp, "\n*Inequality numbers for indices %ld .. %ld : ", lastdv + 1, m + d); - for (i = 1; i <= m - nlinearity; i++) { - fprintf(lrs_ofp, " %ld ", inequality[i]); - } - printA(D, Q); - } - - if (Q->restart) { - if (Q->debug) { - fprintf(lrs_ofp, "\nPivoting to restart co-basis"); - } - if (!restartpivots(D, Q)) { - return FALSE; - } - D->lexflag = lexmin(D, Q, ZERO); /* see if lexmin basis */ - if (Q->debug) { - printA(D, Q); - } - } - /* Check to see if necessary to resize */ - if (Q->inputd > D->d) { - *D_p = resize(D, Q); - } - - return TRUE; -} - -/********* end of lrs_getfirstbasis ***************/ -long getabasis2(lrs_dic *P, lrs_dat *Q, lrs_dic *P2orig, long order[], long linindex[]) - -/* Pivot Ax<=b to standard form */ -/*Try to find a starting basis by pivoting in the variables x[1]..x[d] */ -/*If there are any input linearities, these appear first in order[] */ -/* Steps: (a) Try to pivot out basic variables using order */ -/* Stop if some linearity cannot be made to leave basis */ -/* (b) Permanently remove the cobasic indices of linearities */ -/* (c) If some decision variable cobasic, it is a linearity, */ -/* and will be removed. */ -{ - /* 2015.10.10 linindex now preallocated and received as parameter so we can free it */ - - // static long firsttime = TRUE; /* stays true until first valid dictionary built */ - - long i, j, k; - /* assign local variables to structures */ - lrs_mp_matrix A = P->A; - long *B = P->B; - long *C = P->C; - long *Row = P->Row; - long *Col = P->Col; - long *linearity = Q->linearity; - long *redundcol = Q->redundcol; - long m, d, nlinearity; - long nredundcol = 0L; /* will be calculated here */ - - m = P->m; - d = P->d; - nlinearity = Q->nlinearity; - // 2015.9.15 - /* after first time we update the change in linearities from the last time, saving many pivots */ - if (!FirstTime) { - for (i = 1; i <= m + d; i++) { - linindex[i] = FALSE; - } - if (Q->debug) { - fprintf(lrs_ofp, "\nlindex ="); - } - for (i = 0; i < nlinearity; i++) { - linindex[d + linearity[i]] = TRUE; - if (Q->debug) { - fprintf(lrs_ofp, " %ld", d + linearity[i]); - } - } - - for (i = 1; i <= m; i++) { - if (linindex[B[i]]) { /* pivot out unwanted linearities */ - k = 0; - while (k < d && (linindex[C[k]] || zero(A[Row[i]][Col[k]]))) { - k++; - } - - if (k < d) { - j = i; /* note this index changes in update, cannot use i!) */ - - if (C[k] > B[j]) { /* decrease i or we may skip a linearity */ - i--; - } - pivot(P, Q, j, k); - update(P, Q, &j, &k); - } - else { - /* this is not necessarily an error, eg. two identical rows/cols in payoff matrix */ - if (!zero(A[Row[i]][0])) { /* error condition */ - if (Q->debug || Q->verbose) { - fprintf(lrs_ofp, "\n*Infeasible linearity i=%ld B[i]=%ld", i, B[i]); - if (Q->debug) { - printA(P, Q); - } - } - return (FALSE); - } - if (Q->debug || Q->verbose) { - fprintf(lrs_ofp, "\n*Couldn't remove linearity i=%ld B[i]=%ld", i, B[i]); - } - } - - } /* if linindex */ - } /* for i .. */ - } - else { /* we have not had a successful dictionary built from the given linearities */ - - /* standard lrs processing is done on only the first call to getabasis2 */ - - if (Q->debug) { - fprintf(lrs_ofp, "\ngetabasis from inequalities given in order"); - for (i = 0; i < m; i++) { - fprintf(lrs_ofp, " %ld", order[i]); - } - } - for (j = 0; j < m; j++) { - i = 0; - while (i <= m && B[i] != d + order[j]) { - i++; /* find leaving basis index i */ - } - if (j < nlinearity && i > m) { /* cannot pivot linearity to cobasis */ - if (Q->debug) { - printA(P, Q); - } -#ifndef LRS_QUIET - fprintf(lrs_ofp, "\nCannot find linearity in the basis"); -#endif - return FALSE; - } - if (i <= m) { /* try to do a pivot */ - k = 0; - while (C[k] <= d && zero(A[Row[i]][Col[k]])) { - k++; - } - - if (C[k] <= d) { - pivot(P, Q, i, k); - update(P, Q, &i, &k); - } - else if (j < nlinearity) { /* cannot pivot linearity to cobasis */ - if (zero(A[Row[i]][0])) { -#ifndef LRS_QUIET - fprintf(lrs_ofp, "\n*Input linearity in row %ld is redundant--skipped", order[j]); -#endif - linearity[j] = 0; - } - else { - if (Q->debug) { - printA(P, Q); - } - if (Q->debug || Q->verbose) { - fprintf(lrs_ofp, "\nInconsistent linearities"); - } - return FALSE; - } - } /* end if j < nlinearity */ - - } /* end of if i <= m .... */ - } /* end of for */ - - /* update linearity array to get rid of redundancies */ - i = 0; - k = 0; /* counters for linearities */ - while (k < nlinearity) { - while (k < nlinearity && linearity[k] == 0) { - k++; - } - if (k < nlinearity) { - linearity[i++] = linearity[k++]; - } - } - - nlinearity = i; - /* lrs bug fix, 2009.6.27, nash 2015.9.16 */ - Q->nlinearity = i; - - /* column dependencies now can be recorded */ - /* redundcol contains input column number 0..n-1 where redundancy is */ - k = 0; - while (k < d && C[k] <= d) { - if (C[k] <= d) { /* decision variable still in cobasis */ - redundcol[nredundcol++] = C[k] - Q->hull; /* adjust for hull indices */ - } - k++; - } - - /* now we know how many decision variables remain in problem */ - Q->nredundcol = nredundcol; - Q->lastdv = d - nredundcol; - /* 2015.9.15 bug fix : we needed first *successful* time */ - FirstTime = FALSE; - } /* else firsttime ... we have built a dictionary from the given linearities */ - - /* we continue from here after loading dictionary */ - - if (Q->debug) { - fprintf(lrs_ofp, "\nend of first phase of getabasis2: "); - fprintf(lrs_ofp, "lastdv=%ld nredundcol=%ld", Q->lastdv, Q->nredundcol); - fprintf(lrs_ofp, "\nredundant cobases:"); - for (i = 0; i < nredundcol; i++) { - fprintf(lrs_ofp, " %ld", redundcol[i]); - } - printA(P, Q); - } - - /* here we save dictionary for use next time, *before* we resize */ - - copy_dict(Q, P2orig, P); - - /* Remove linearities from cobasis for rest of computation */ - /* This is done in order so indexing is not screwed up */ - - for (i = 0; i < nlinearity; i++) { /* find cobasic index */ - k = 0; - while (k < d && C[k] != linearity[i] + d) { - k++; - } - if (k >= d) { - if (Q->debug || Q->verbose) { - fprintf(lrs_ofp, "\nCould not remove cobasic index"); - } - /* not neccesarily an error as eg., could be repeated row/col in payoff */ - } - else { - removecobasicindex(P, Q, k); - d = P->d; - } - } - if (Q->debug && nlinearity > 0) { - printA(P, Q); - } - /* set index value for first slack variable */ - - /* Check feasability */ - if (Q->givenstart) { - i = Q->lastdv + 1; - while (i <= m && !negative(A[Row[i]][0])) { - i++; - } - if (i <= m) { - fprintf(lrs_ofp, "\n*Infeasible startingcobasis - will be modified"); - } - } - return TRUE; -} /* end of getabasis2 */ - -long lrs_nashoutput(lrs_dat *Q, lrs_mp_vector output, long player) -{ - long i; - long origin = TRUE; - - /* do not print the origin for either player */ - for (i = 1; i < Q->n; i++) { - if (!zero(output[i])) { - origin = FALSE; - } - } - - if (origin) { - return FALSE; - } - - fprintf(lrs_ofp, "%ld ", player); - for (i = 1; i < Q->n; i++) { - prat("", output[i], output[0]); - } - fprintf(lrs_ofp, "\n"); - fflush(lrs_ofp); - return TRUE; -} /* end lrs_nashoutput */ - -//======================================================================== -// Old style solver. Included for backward compatibility -//======================================================================== -int lrs_solve_nash_legacy(int argc, char *argv[]) -// Handles legacy input files -{ - lrs_dic *P1 /*,*P2*/; /* structure for holding current dictionary and indices */ - lrs_dat *Q1, *Q2; /* structure for holding static problem data */ - - lrs_mp_vector output1; /* holds one line of output; ray,vertex,facet,linearity */ - lrs_mp_vector output2; /* holds one line of output; ray,vertex,facet,linearity */ - lrs_mp_matrix Lin; /* holds input linearities if any are found */ - lrs_mp_matrix A2orig; - lrs_dic *P2orig; /* we will save player 2's dictionary in getabasis */ - - long *linindex; /* for faster restart of player 2 */ - - long col; /* output column index for dictionary */ - long startcol = 0; - long prune = FALSE; /* if TRUE, getnextbasis will prune tree and backtrack */ - long numequilib = 0; /* number of nash equilibria found */ - long oldnum = 0; - - /* global variables lrs_ifp and lrs_ofp are file pointers for input and output */ - /* they default to stdin and stdout, but may be overidden by command line parms. */ - - if (argc <= 2) { - printf("Usage: %s input1 input2 [outputfile] \n", argv[0]); - return 1; - } - - /*************************************************** - Step 0: - Do some global initialization that should only be done once, - no matter how many lrs_dat records are allocated. db - - ***************************************************/ - - if (!lrs_init("\n*nash:")) { - return 1; - } - printf("\n"); - printf(AUTHOR); - - /*********************************************************************************/ - /* Step 1: Allocate lrs_dat, lrs_dic and set up the problem */ - /*********************************************************************************/ - - Q1 = lrs_alloc_dat("LRS globals"); /* allocate and init structure for static problem data */ - - if (Q1 == NULL) { - return 1; - } - Q1->nash = TRUE; - - if (!lrs_read_dat(Q1, argc, argv)) { /* read first part of problem data to get dimensions */ - return 1; /* and problem type: H- or V- input representation */ - } - - P1 = lrs_alloc_dic(Q1); /* allocate and initialize lrs_dic */ - if (P1 == NULL) { - return 1; - } - - if (!lrs_read_dic(P1, Q1)) { /* read remainder of input to setup P1 and Q1 */ - return 1; - } - - output1 = - lrs_alloc_mp_vector(Q1->n + Q1->m); /* output holds one line of output from dictionary */ - - fclose(lrs_ifp); - - /* allocate and init structure for player 2's problem data */ - - printf("\n*Second input taken from file %s\n", argv[2]); - Q2 = lrs_alloc_dat("LRS globals"); - if (Q2 == NULL) { - return 1; - } - - Q2->nash = TRUE; - - if (!lrs_read_dat(Q2, 2, argv)) { /* read first part of problem data to get dimensions */ - return 1; /* and problem type: H- or V- input representation */ - } - - if (Q2->nlinearity > 0) { - free(Q2->linearity); /* we will start again */ - } - Q2->linearity = CALLOC((Q2->m + 2), sizeof(long)); - - P2orig = lrs_alloc_dic(Q2); /* allocate and initialize lrs_dic */ - if (P2orig == NULL) { - return 1; - } - if (!lrs_read_dic(P2orig, Q2)) { /* read remainder of input to setup P2 and Q2 */ - return 1; - } - A2orig = P2orig->A; - - output2 = - lrs_alloc_mp_vector(Q1->n + Q1->m); /* output holds one line of output from dictionary */ - - linindex = calloc((P2orig->m + P2orig->d + 2), sizeof(long)); /* for next time*/ - - fprintf(lrs_ofp, "\n***** %ld %ld rational\n", Q1->n, Q2->n); - - /*********************************************************************************/ - /* Step 2: Find a starting cobasis from default of specified order */ - /* P1 is created to hold active dictionary data and may be cached */ - /* Lin is created if necessary to hold linearity space */ - /* Print linearity space if any, and retrieve output from first dict. */ - /*********************************************************************************/ - - if (!lrs_getfirstbasis(&P1, Q1, &Lin, TRUE)) { - return 1; - } - - if (Q1->dualdeg) { - printf("\n*Warning! Dual degenerate, ouput may be incomplete"); - printf("\n*Recommendation: Add dualperturb option before maximize in first input file\n"); - } - - if (Q1->unbounded) { - printf("\n*Warning! Unbounded starting dictionary for p1, output may be incomplete"); - printf("\n*Recommendation: Change/remove maximize option, or include bounds \n"); - } - - /* Pivot to a starting dictionary */ - /* There may have been column redundancy */ - /* If so the linearity space is obtained and redundant */ - /* columns are removed. User can access linearity space */ - /* from lrs_mp_matrix Lin dimensions nredundcol x d+1 */ - - if (Q1->homogeneous && Q1->hull) { - startcol++; /* col zero not treated as redundant */ - } - - for (col = startcol; col < Q1->nredundcol; col++) { /* print linearity space */ - lrs_printoutput(Q1, Lin[col]); /* Array Lin[][] holds the coeffs. */ - } - - /*********************************************************************************/ - /* Step 3: Terminate if lponly option set, otherwise initiate a reverse */ - /* search from the starting dictionary. Get output for each new dict. */ - /*********************************************************************************/ - - /* We initiate reverse search from this dictionary */ - /* getting new dictionaries until the search is complete */ - /* User can access each output line from output which is */ - /* vertex/ray/facet from the lrs_mp_vector output */ - /* prune is TRUE if tree should be pruned at current node */ - - do { - prune = lrs_checkbound(P1, Q1); - if (!prune && lrs_getsolution(P1, Q1, output1, col)) { - oldnum = numequilib; - nash2_main(P1, Q1, P2orig, Q2, &numequilib, output2, linindex); - if (numequilib > oldnum || Q1->verbose) { - if (Q1->verbose) { - prat(" \np2's obj value: ", P1->objnum, P1->objden); - } - lrs_nashoutput(Q1, output1, 1L); - fprintf(lrs_ofp, "\n"); - } - } - } while (lrs_getnextbasis(&P1, Q1, prune)); - - fprintf(lrs_ofp, "\n*Number of equilibria found: %ld", numequilib); - fprintf(lrs_ofp, "\n*Player 1: vertices=%ld bases=%ld pivots=%ld", Q1->count[1], Q1->count[2], - Q1->count[3]); - fprintf(lrs_ofp, "\n*Player 2: vertices=%ld bases=%ld pivots=%ld", Q2->count[1], Q2->count[2], - Q2->count[3]); - - lrs_clear_mp_vector(output1, Q1->m + Q1->n); - lrs_clear_mp_vector(output2, Q1->m + Q1->n); - - lrs_free_dic(P1, Q1); /* deallocate lrs_dic */ - lrs_free_dat(Q1); /* deallocate lrs_dat */ - - /* 2015.10.10 new code to clear P2orig */ - Q2->Qhead = P2orig; /* reset this or you crash free_dic */ - P2orig->A = A2orig; /* reset this or you crash free_dic */ - - lrs_free_dic(P2orig, Q2); /* deallocate lrs_dic */ - lrs_free_dat(Q2); /* deallocate lrs_dat */ - - free(linindex); - - lrs_close("nash:"); - - return 0; -} - -/*********************************************/ -/* end of nash driver */ -/*********************************************/ - -//========================================================================== -// Building the problem representations (adapted from Gambit-enummixed) -//========================================================================== - -// -// These two functions are based upon the program setupnash.c from the -// lrslib distribution, and the user's guide documentation. -// There are two separate functions, one for each player's problem. -// According to the user's guide, the ordering of the constraint rows -// is significant, and differs between the players; for player 1's problem -// the nonnegativity constraints come first, whereas for player 2's problem -// they appear later. Experiments suggest this is in fact true, and -// reversing them breaks something. -// - -//----------------------------------------------------------------------------------------// -void FillNonnegativityRows(lrs_dic *P, lrs_dat *Q, int firstRow, int lastRow, int n) -{ - const int MAXCOL = 1000; /* maximum number of columns */ - long num[1000], den[1000]; - long row, col; - - for (row = firstRow; row <= lastRow; row++) { - num[0] = 0; - den[0] = 1; - - for (col = 1; col < n; col++) { - num[col] = (row - firstRow + 1 == col) ? 1 : 0; - den[col] = 1; - } - lrs_set_row(P, Q, row, num, den, GE); - } -} - -//----------------------------------------------------------------------------------------// -void FillConstraintRows(lrs_dic *P, lrs_dat *Q, const game *g, int p1, int p2, int firstRow) -{ - const int MAXCOL = 1000; /* maximum number of columns */ - long num[1000], den[1000]; - ratnum x; - int row, s, t; - - for (row = firstRow; row < firstRow + g->nstrats[p1]; row++) { - num[0] = 0; - den[0] = 1; - s = row - firstRow; - for (t = 0; t < g->nstrats[p2]; t++) { - x = p1 == ROW ? g->payoff[s][t][p1] : g->payoff[t][s][p1]; - num[t + 1] = -x.num; - den[t + 1] = x.den; - } - num[g->nstrats[p2] + 1] = 1; - den[g->nstrats[p2] + 1] = 1; - lrs_set_row(P, Q, row, num, den, GE); - } -} - -//----------------------------------------------------------------------------------------// -void FillLinearityRow(lrs_dic *P, lrs_dat *Q, int m, int n) -{ - const int MAXCOL = 1000; /* maximum number of columns */ - long num[1000], den[1000]; - int i; - - num[0] = -1; - den[0] = 1; - - for (i = 1; i < n - 1; i++) { - num[i] = 1; - den[i] = 1; - } - - num[n - 1] = 0; - den[n - 1] = 1; - - lrs_set_row(P, Q, m, num, den, EQ); -} - -// -// TL added this to get first row of ones. Don't know if it's needed -//----------------------------------------------------------------------------------------// -void FillFirstRow(lrs_dic *P, lrs_dat *Q, int n) -{ - const int MAXCOL = 1000; /* maximum number of columns */ - long num[1000], den[1000]; - int i; - - for (i = 0; i < n; i++) { - num[i] = 1; - den[i] = 1; - } - lrs_set_row(P, Q, 0, num, den, GE); -} - -// -// Build the H-representation for player p1 -//----------------------------------------------------------------------------------------// -void BuildRep(lrs_dic *P, lrs_dat *Q, const game *g, int p1, int p2) -{ - long m = Q->m; /* number of inequalities */ - long n = Q->n; - - if (p1 == 0) { - FillConstraintRows(P, Q, g, p1, p2, 1); - FillNonnegativityRows(P, Q, g->nstrats[p1] + 1, g->nstrats[ROW] + g->nstrats[COL], n); - } - else { - FillNonnegativityRows(P, Q, 1, g->nstrats[p2], n); - FillConstraintRows(P, Q, g, p1, p2, g->nstrats[p2] + 1); // 1 here - } - FillLinearityRow(P, Q, m, n); - - // TL added this to get first row of ones. (Is this necessary?) - FillFirstRow(P, Q, n); -} - -//----------------------------------------------------------------------------------------// -void printGame(game *g) -{ - int s, t; - char out[2][MAXINPUT]; - fprintf(lrs_ofp, - "\n--------------------------------------------------------------------------------\n"); - fprintf(lrs_ofp, "%s payoff matrix:\n", ((gInfo *)g->aux)->name); - for (s = 0; s < g->nstrats[ROW]; s++) { - for (t = 0; t < g->nstrats[COL]; t++) { - if (g->payoff[s][t][ROW].den == 1) { - sprintf(out[ROW], "%ld,", g->payoff[s][t][ROW].num); - } - else { - sprintf(out[ROW], "%ld/%ld,", g->payoff[s][t][ROW].num, g->payoff[s][t][ROW].den); - } - if (g->payoff[s][t][COL].den == 1) { - sprintf(out[COL], "%ld", g->payoff[s][t][COL].num); - } - else { - sprintf(out[COL], "%ld/%ld", g->payoff[s][t][COL].num, g->payoff[s][t][COL].den); - } - fprintf(lrs_ofp, "%*s%-*s ", ((gInfo *)g->aux)->fwidth[t][ROW] + 1, out[ROW], - ((gInfo *)g->aux)->fwidth[t][COL], out[COL]); - } - fprintf(lrs_ofp, "\n"); - } - fprintf(lrs_ofp, "\nNash equilibria:\n"); - fflush(lrs_ofp); -} - -// Functions to set field widths for pretty printing of payoff matrices -void setFwidth(game *g, int len) -{ - int pos, t; - for (t = 0; t < g->nstrats[COL]; t++) { - for (pos = 0; pos < 2; pos++) { - ((gInfo *)g->aux)->fwidth[t][pos] = len; - } - } -} - -void initFwidth(game *g) -{ - int pos, t; - for (t = 0; t < g->nstrats[COL]; t++) { - for (pos = 0; pos < 2; pos++) { - ((gInfo *)g->aux)->fwidth[t][pos] = 0; - } - } -} - -void updateFwidth(game *g, int col, int pos, char *str) -{ - int len = strlen(str); - if (len > ((gInfo *)g->aux)->fwidth[col][pos]) { - ((gInfo *)g->aux)->fwidth[col][pos] = len; - } -} - -/******************** end of lrsnashlib.c ***************************/ diff --git a/src/solvers/lrs/lrsnashlib.h b/src/solvers/lrs/lrsnashlib.h deleted file mode 100644 index e73630e39..000000000 --- a/src/solvers/lrs/lrsnashlib.h +++ /dev/null @@ -1,68 +0,0 @@ -/*******************************************************/ -/* lrsnashlib is a library of routines for computing */ -/* computing all nash equilibria for two person games */ -/* given by mxn payoff matrices A,B */ -/* */ -/* */ -/* Main user callable function is */ -/* lrs_solve_nash(game *g) */ -/* */ -/* Sample driver: lrsnash.c */ -/* Derived from nash.c in lrslib-060 */ -/* by Terje Lensberg, October 26, 2015: */ -/*******************************************************/ - -/*************/ -/* Games */ -/*************/ - -#define MAXSTRAT 200 -#define ROW 0 -#define COL 1 - -typedef struct { - long num; - long den; -} ratnum; - -typedef struct { - long nstrats[2]; - ratnum payoff[MAXSTRAT][MAXSTRAT][2]; - // For auxiliary information - void *aux; -} game; - -typedef struct { - char name[100]; - int fwidth[MAXSTRAT][2]; // Column field widths (for output) -} gInfo; - -int lrs_solve_nash(game *g); - -long nash2_main(lrs_dic *P1, lrs_dat *Q1, lrs_dic *P2orig, lrs_dat *Q2, long *numequilib, - lrs_mp_vector output, long linindex[]); -/* lrs driver, argv[2]= 2nd input file for nash equilibria */ - -long lrs_getfirstbasis2(lrs_dic **D_p, lrs_dat *Q, lrs_dic *P2orig, lrs_mp_matrix *Lin, - long no_output, long linindex[]); - -long getabasis2(lrs_dic *P, lrs_dat *Q, lrs_dic *P2orig, long order[], long linindex[]); - -long lrs_nashoutput(lrs_dat *Q, lrs_mp_vector output, long player); -/* returns TRUE and prints output if not the origin */ - -int lrs_solve_nash_legacy(int argc, char *argv[]); - -void BuildRep(lrs_dic *P, lrs_dat *Q, const game *g, int p1, int p2); -void FillFirstRow(lrs_dic *P, lrs_dat *Q, int n); -void FillLinearityRow(lrs_dic *P, lrs_dat *Q, int m, int n); -void FillConstraintRows(lrs_dic *P, lrs_dat *Q, const game *g, int p1, int p2, int firstRow); -void FillNonnegativityRows(lrs_dic *P, lrs_dat *Q, int firstRow, int lastRow, int n); -void printGame(game *g); -void setFwidth(game *g, int len); -void initFwidth(game *g); -void updateFwidth(game *g, int col, int pos, char *str); - -long FirstTime; /* set this to true for every new game to be solved */ -static long Debug_flag; -static long Verbose_flag; diff --git a/src/tools/enummixed/enummixed.cc b/src/tools/enummixed/enummixed.cc index 615de3925..c98c4eabe 100644 --- a/src/tools/enummixed/enummixed.cc +++ b/src/tools/enummixed/enummixed.cc @@ -46,8 +46,6 @@ void PrintBanner(std::ostream &p_stream) { p_stream << "Compute Nash equilibria by enumerating extreme points\n"; p_stream << "Gambit version " VERSION ", Copyright (C) 1994-2024, The Gambit Project\n"; - p_stream << "Enumeration code based on lrslib 6.2,\n"; - p_stream << "Copyright (C) 1995-2016 by David Avis (avis@cs.mcgill.ca)\n"; p_stream << "This is free software, distributed under the GNU GPL\n\n"; } @@ -62,7 +60,6 @@ void PrintHelp(char *progname) std::cerr << " -d DECIMALS compute using floating-point arithmetic;\n"; std::cerr << " display results with DECIMALS digits\n"; std::cerr << " -D don't eliminate dominated strategies first\n"; - std::cerr << " -L use lrslib for enumeration (experimental!)\n"; std::cerr << " -c output connectedness information\n"; std::cerr << " -h, --help print this help message\n"; std::cerr << " -q quiet mode (suppresses banner)\n"; @@ -80,7 +77,7 @@ int main(int argc, char *argv[]) int long_opt_index = 0; struct option long_options[] = { {"help", 0, nullptr, 'h'}, {"version", 0, nullptr, 'v'}, {nullptr, 0, nullptr, 0}}; - while ((c = getopt_long(argc, argv, "d:DvhqcSL", long_options, &long_opt_index)) != -1) { + while ((c = getopt_long(argc, argv, "d:DvhqcS", long_options, &long_opt_index)) != -1) { switch (c) { case 'v': PrintBanner(std::cerr); @@ -92,9 +89,6 @@ int main(int argc, char *argv[]) case 'D': eliminate = false; break; - case 'L': - uselrs = true; - break; case 'h': PrintHelp(argv[0]); break; @@ -138,13 +132,7 @@ int main(int argc, char *argv[]) try { Game game = ReadGame(*input_stream); - if (uselrs) { - std::shared_ptr> renderer( - new MixedStrategyCSVRenderer(std::cout)); - EnumMixedLrsStrategySolver solver(renderer); - solver.Solve(game); - } - else if (useFloat) { + if (useFloat) { std::shared_ptr> renderer( new MixedStrategyCSVRenderer(std::cout, numDecimals)); EnumMixedStrategySolver solver(renderer); From 008b7e8c6b7a3a3eeb899f595ebfac2b47d38b3d Mon Sep 17 00:00:00 2001 From: Ted Turocy Date: Wed, 23 Oct 2024 12:57:17 +0100 Subject: [PATCH 24/99] Modernise and simplify StrategySupportProfile implementation. This cleans up various aspects of technical debt in StrategySupportProfile and related classes: * Using STL containers instead of Gambit ones * Defining appropriate abstractions * Preferring STL-style algorithms to manually-crafted ones * Avoiding using explicit 1-based indexes in favour of contaniner-iterator idiom. --- src/core/array.h | 2 +- src/core/rational.cc | 2 + src/core/rational.h | 1 + src/gambit.h | 6 + src/games/game.cc | 14 +- src/games/gameagg.cc | 6 +- src/games/gamebagg.cc | 6 +- src/games/stratmixed.h | 6 +- src/games/stratpure.cc | 91 +++------- src/games/stratpure.h | 21 +-- src/games/stratspt.cc | 249 ++++++++++---------------- src/games/stratspt.h | 54 +++--- src/games/writer.cc | 12 +- src/gui/nfgtable.cc | 43 +++-- src/pygambit/gambit.pxd | 23 ++- src/pygambit/game.pxi | 26 ++- src/pygambit/nashphc.py | 11 +- src/pygambit/stratspt.pxi | 102 +++++++---- src/pygambit/supports.py | 11 +- src/solvers/enumpoly/nfgpoly.cc | 2 +- src/solvers/nashsupport/nfgsupport.cc | 5 - tests/test_stratprofiles.py | 79 ++++---- 22 files changed, 380 insertions(+), 392 deletions(-) diff --git a/src/core/array.h b/src/core/array.h index c42f9a089..be4923880 100644 --- a/src/core/array.h +++ b/src/core/array.h @@ -73,7 +73,7 @@ template class Array { 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++() diff --git a/src/core/rational.cc b/src/core/rational.cc index fe6df6e1d..ddad5a189 100644 --- a/src/core/rational.cc +++ b/src/core/rational.cc @@ -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) { diff --git a/src/core/rational.h b/src/core/rational.h index 37b902130..f8f6295b3 100644 --- a/src/core/rational.h +++ b/src/core/rational.h @@ -44,6 +44,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); diff --git a/src/gambit.h b/src/gambit.h index 214aec972..cd10edcb6 100644 --- a/src/gambit.h +++ b/src/gambit.h @@ -28,6 +28,7 @@ #include #include #include +#include #include namespace Gambit { @@ -53,6 +54,11 @@ 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 { diff --git a/src/games/game.cc b/src/games/game.cc index 8dadcb94b..408805169 100644 --- a/src/games/game.cc +++ b/src/games/game.cc @@ -312,15 +312,27 @@ void GameRep::WriteNfgFile(std::ostream &p_file) const template MixedStrategyProfileRep::MixedStrategyProfileRep(const StrategySupportProfile &p_support) : m_probs(p_support.MixedProfileLength()), m_support(p_support), + m_profileIndex(p_support.GetGame()->MixedProfileLength()), m_gameversion(p_support.GetGame()->GetVersion()) { + int index = 1, stnum = 1; + for (auto player : p_support.GetGame()->GetPlayers()) { + for (auto strategy : player->GetStrategies()) { + if (p_support.Contains(strategy)) { + m_profileIndex[stnum++] = index++; + } + else { + m_profileIndex[stnum++] = -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; } diff --git a/src/games/gameagg.cc b/src/games/gameagg.cc index fa99371a2..73d019ee1 100644 --- a/src/games/gameagg.cc +++ b/src/games/gameagg.cc @@ -103,7 +103,7 @@ template T AGGMixedStrategyProfileRep::GetPayoff(int pl) const 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()]; + int ind = this->m_profileIndex[strategy->GetId()]; s[g.aggPtr->firstAction(i) + j] = (ind == -1) ? (T)0 : this->m_probs[ind]; } } @@ -125,7 +125,7 @@ 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 int &ind = this->m_profileIndex[strategy->GetId()]; s[g.aggPtr->firstAction(i) + j] = (ind == -1) ? (T)0 : this->m_probs[ind]; } } @@ -161,7 +161,7 @@ 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 int &ind = this->m_profileIndex[strategy->GetId()]; s[g.aggPtr->firstAction(i) + j] = (ind == -1) ? (T)0 : this->m_probs[ind]; } } diff --git a/src/games/gamebagg.cc b/src/games/gamebagg.cc index 31a32b307..7a1deefb2 100644 --- a/src/games/gamebagg.cc +++ b/src/games/gamebagg.cc @@ -115,7 +115,7 @@ template T BAGGMixedStrategyProfileRep::GetPayoff(int pl) const 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()]; + int ind = this->m_profileIndex[strategy->GetId()]; s.at(offs) = (ind == -1) ? (T)0 : this->m_probs[ind]; } } @@ -146,7 +146,7 @@ T BAGGMixedStrategyProfileRep::GetPayoffDeriv(int pl, const GameStrategy &ps) 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()]; + int ind = this->m_profileIndex[strategy->GetId()]; s.at(g.baggPtr->firstAction(i, tp) + j) = (ind == -1) ? Rational(0) : this->m_probs[ind]; } } @@ -192,7 +192,7 @@ T BAGGMixedStrategyProfileRep::GetPayoffDeriv(int pl, const GameStrategy &ps1 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()]; + int ind = this->m_profileIndex[strategy->GetId()]; s.at(g.baggPtr->firstAction(i, tp) + j) = static_cast((ind == -1) ? T(0) : this->m_probs[ind]); } diff --git a/src/games/stratmixed.h b/src/games/stratmixed.h index ea665beb2..1b6f010a5 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) + Array m_profileIndex; unsigned int m_gameversion; explicit MixedStrategyProfileRep(const StrategySupportProfile &); @@ -44,12 +46,12 @@ 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[p_strategy->GetId()]]; } /// Returns the probability the strategy is played T &operator[](const GameStrategy &p_strategy) { - return m_probs[m_support.m_profileIndex[p_strategy->GetId()]]; + return m_probs[m_profileIndex[p_strategy->GetId()]]; } virtual T GetPayoff(int pl) const = 0; diff --git a/src/games/stratpure.cc b/src/games/stratpure.cc index eec80258f..29758e1a9 100644 --- a/src/games/stratpure.cc +++ b/src/games/stratpure.cc @@ -111,87 +111,38 @@ MixedStrategyProfile PureStrategyProfileRep::ToMixedStrategyProfile() // 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) -{ - 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(); -} - -//--------------------------------------------------------------------------- -// Iteration -//--------------------------------------------------------------------------- - -void StrategyProfileIterator::First() + const std::vector &p_frozen) + : m_support(p_support), m_profile(p_support.GetGame()->NewPureStrategyProfile()), + m_frozen(p_frozen) { - for (int pl = 1; pl <= m_support.GetGame()->NumPlayers(); pl++) { - if (pl == m_frozen1 || pl == m_frozen2) { - continue; + for (auto strategy : m_frozen) { + m_profile->SetStrategy(strategy); + } + 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); + m_currentStrat[player] = m_support.GetStrategies(player).begin(); + m_profile->SetStrategy(*m_currentStrat[player]); } - m_profile->SetStrategy(m_support.GetStrategy(pl, 1)); - m_currentStrat[pl] = 1; } } void StrategyProfileIterator::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; + for (auto player : m_unfrozen) { + ++m_currentStrat[player]; + if (m_currentStrat[player] != m_support.GetStrategies(player).end()) { + m_profile->SetStrategy(*m_currentStrat[player]); return; } + m_currentStrat[player] = m_support.GetStrategies(player).begin(); + m_profile->SetStrategy(*m_currentStrat[player]); } + m_atEnd = true; } } // namespace Gambit diff --git a/src/games/stratpure.h b/src/games/stratpure.h index 398b20743..ef2c0bf25 100644 --- a/src/games/stratpure.h +++ b/src/games/stratpure.h @@ -137,26 +137,19 @@ class StrategyProfileIterator { friend class GameTableRep; private: - bool m_atEnd; + bool m_atEnd{false}; StrategySupportProfile m_support; - Array m_currentStrat; + std::map 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); + /// Construct a new iterator on the support + explicit StrategyProfileIterator(const StrategySupportProfile &, + const std::vector &p_frozen = {}); //@} /// @name Iteration and data access diff --git a/src/games/stratspt.cc b/src/games/stratspt.cc index 7835a04bc..f0f956b9d 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,20 +70,15 @@ 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; } @@ -124,72 +110,29 @@ void StrategySupportProfile::WriteNfgFile(std::ostream &p_file) const }; } -//--------------------------------------------------------------------------- -// 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; - } + 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); } - - // 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]++; - } - } - 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; } @@ -225,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; } } @@ -244,86 +185,88 @@ bool StrategySupportProfile::IsDominated(const GameStrategy &s, bool p_strict, } } -bool StrategySupportProfile::Undominated(StrategySupportProfile &newS, const GamePlayer &p_player, - bool p_strict, bool p_external) const +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) { - Array set((p_external) ? p_player->NumStrategies() - : NumStrategies(p_player->GetNumber())); - - if (p_external) { - for (int st = 1; st <= set.Length(); st++) { - set[st] = p_player->GetStrategy(st); - } - } - else { - for (int st = 1; st <= set.Length(); st++) { - set[st] = GetStrategy(p_player->GetNumber(), st); - } - } - - int min = 0, dis = set.Length() - 1; - + 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)) { + 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)) { - 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]); - } - return true; +} // end anonymous namespace + +bool UndominatedForPlayer(const StrategySupportProfile &p_support, + StrategySupportProfile &p_newSupport, const GamePlayer &p_player, + bool p_strict, bool p_external) +{ + std::vector set((p_external) ? p_player->NumStrategies() + : 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()); + } + 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 min != set.end(); } StrategySupportProfile StrategySupportProfile::Undominated(bool p_strict, bool p_external) const { - StrategySupportProfile newS(*this); - + StrategySupportProfile newSupport(*this); for (auto player : m_nfg->GetPlayers()) { - Undominated(newS, player, p_strict, p_external); + UndominatedForPlayer(*this, newSupport, player, p_strict, p_external); } - - return newS; -} - -StrategySupportProfile StrategySupportProfile::Undominated(bool p_strict, - const GamePlayer &p_player) const -{ - StrategySupportProfile newS(*this); - Undominated(newS, p_player, p_strict); - return newS; + return newSupport; } //--------------------------------------------------------------------------- diff --git a/src/games/stratspt.h b/src/games/stratspt.h index 75df1bfe1..bf9e6c1c4 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; + +public: + class Support { + private: + const StrategySupportProfile *m_profile; + GamePlayer m_player; - /// The index into a strategy profile for a strategy (-1 if not in support) - Array m_profileIndex; + public: + using const_iterator = std::vector::const_iterator; - bool Undominated(StrategySupportProfile &newS, const GamePlayer &, bool p_strict, - bool p_external = false) const; + Support() : m_profile(0), m_player(0) {} + 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(); } /// 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 Support(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 @@ -140,8 +140,6 @@ class StrategySupportProfile { /// Returns a copy of the support with dominated strategies removed StrategySupportProfile Undominated(bool p_strict, bool p_external = false) const; - /// Returns a copy of the support with dominated strategies of the specified player removed - StrategySupportProfile Undominated(bool p_strict, const GamePlayer &p_player) const; //@} /// @name Identification of overwhelmed strategies diff --git a/src/games/writer.cc b/src/games/writer.cc index a60bb4ed3..e5f317f6b 100644 --- a/src/games/writer.cc +++ b/src/games/writer.cc @@ -30,8 +30,10 @@ std::string HTMLGameWriter::Write(const Game &p_game, int p_rowPlayer, int p_col std::string theHtml; theHtml += "

" + p_game->GetTitle() + "

\n"; - for (StrategyProfileIterator iter(p_game, p_rowPlayer, 1, p_colPlayer, 1); !iter.AtEnd(); - iter++) { + for (StrategyProfileIterator iter(p_game, + {p_game->GetPlayer(p_rowPlayer)->GetStrategies().front(), + p_game->GetPlayer(p_colPlayer)->GetStrategies().front()}); + !iter.AtEnd(); iter++) { if (p_game->NumPlayers() > 2) { theHtml += "
Subtable with strategies:
"; for (int pl = 1; pl <= p_game->NumPlayers(); pl++) { @@ -97,8 +99,10 @@ std::string LaTeXGameWriter::Write(const Game &p_game, int p_rowPlayer, int p_co { std::string theHtml; - for (StrategyProfileIterator iter(p_game, p_rowPlayer, 1, p_colPlayer, 1); !iter.AtEnd(); - iter++) { + for (StrategyProfileIterator iter(p_game, + {p_game->GetPlayer(p_rowPlayer)->GetStrategies().front(), + p_game->GetPlayer(p_colPlayer)->GetStrategies().front()}); + !iter.AtEnd(); iter++) { theHtml += "\\begin{game}{"; theHtml += lexical_cast(p_game->GetPlayer(p_rowPlayer)->NumStrategies()); theHtml += "}{"; diff --git a/src/gui/nfgtable.cc b/src/gui/nfgtable.cc index 78652dd23..d4ba9e534 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()) { @@ -207,7 +213,7 @@ void gbtRowPlayerWidget::OnCellRightClick(wxSheetEvent &p_event) int player = m_table->GetRowPlayer(coords.GetCol() + 1); 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) @@ -225,7 +231,7 @@ wxString gbtRowPlayerWidget::GetCellValue(const wxSheetCoords &p_coords) int player = m_table->GetRowPlayer(p_coords.GetCol() + 1); 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) @@ -235,7 +241,7 @@ void gbtRowPlayerWidget::SetCellValue(const wxSheetCoords &p_coords, const wxStr int player = m_table->GetRowPlayer(p_coords.GetCol() + 1); 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 @@ -268,7 +274,7 @@ 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); + Gambit::GameStrategy strategy = GetStrategy(support, player, strat); if (support.IsDominated(strategy, false)) { wxRect rect = CellToRect(p_coords); @@ -425,7 +431,7 @@ void gbtColPlayerWidget::OnCellRightClick(wxSheetEvent &p_event) int player = m_table->GetColPlayer(coords.GetRow() + 1); 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() @@ -481,7 +487,7 @@ wxString gbtColPlayerWidget::GetCellValue(const wxSheetCoords &p_coords) int player = m_table->GetColPlayer(p_coords.GetRow() + 1); 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) @@ -491,7 +497,7 @@ void gbtColPlayerWidget::SetCellValue(const wxSheetCoords &p_coords, const wxStr int player = m_table->GetColPlayer(p_coords.GetRow() + 1); 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 @@ -524,7 +530,7 @@ 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); + Gambit::GameStrategy strategy = GetStrategy(support, player, strat); if (support.IsDominated(strategy, false)) { wxRect rect = CellToRect(p_coords); @@ -1071,11 +1077,16 @@ void gbtTableWidget::SetRowPlayer(int index, int pl) 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,7 +1095,7 @@ 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; } @@ -1092,7 +1103,7 @@ int gbtTableWidget::NumRowsSpanned(int index) const int gbtTableWidget::RowToStrategy(int player, int row) const { int strat = row / NumRowsSpanned(player); - return (strat % m_doc->GetNfgSupport().NumStrategies(GetRowPlayer(player)) + 1); + return (strat % NumStrategies(m_doc->GetNfgSupport(), GetRowPlayer(player)) + 1); } void gbtTableWidget::SetColPlayer(int index, int pl) @@ -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,7 +1138,7 @@ 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; } @@ -1135,7 +1146,7 @@ int gbtTableWidget::NumColsSpanned(int index) const 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); + return (strat % NumStrategies(m_doc->GetNfgSupport(), GetColPlayer(player)) + 1); } Gambit::PureStrategyProfile gbtTableWidget::CellToProfile(const wxSheetCoords &p_coords) const @@ -1145,12 +1156,12 @@ Gambit::PureStrategyProfile gbtTableWidget::CellToProfile(const wxSheetCoords &p 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()))); + 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()))); + profile->SetStrategy(GetStrategy(support, player, ColToStrategy(i, p_coords.GetCol()))); } return profile; diff --git a/src/pygambit/gambit.pxd b/src/pygambit/gambit.pxd index 8aaa6d2fc..265eb682c 100644 --- a/src/pygambit/gambit.pxd +++ b/src/pygambit/gambit.pxd @@ -26,10 +26,18 @@ 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 + Array() except + Array(int) except + + iterator begin() except + + iterator end() except + + cdef extern from "core/list.h": cdef cppclass c_List "List"[T]: @@ -188,6 +196,7 @@ cdef extern from "games/game.h": int NumPlayers() except + c_GamePlayer GetPlayer(int) except +IndexError + Array[c_GamePlayer] GetPlayers() except + c_GamePlayer GetChance() except + c_GamePlayer NewPlayer() except + @@ -331,6 +340,17 @@ cdef extern from "games/behavmixed.h": 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 + @@ -339,10 +359,9 @@ 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 diff --git a/src/pygambit/game.pxi b/src/pygambit/game.pxi index 8b116f62f..ae26e5600 100644 --- a/src/pygambit/game.pxi +++ b/src/pygambit/game.pxi @@ -952,8 +952,30 @@ class Game: profile[action] = Rational(hi - lo - 1, denom) return profile - def support_profile(self): - return StrategySupportProfile(self) + def strategy_support_profile( + self, strategies: typing.Callable | None = None + ) -> StrategySupportProfile: + """Create a new `StrategySupportProfile` on the game. + + 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(self) + if strategies is not None: + for strategy in self.strategies: + if not strategies(strategy): + if not (deref(profile.support) + .RemoveStrategy(cython.cast(Strategy, strategy).strategy)): + raise ValueError("attempted to remove the last strategy for player") + return profile def nodes( self, diff --git a/src/pygambit/nashphc.py b/src/pygambit/nashphc.py index 98e36f332..2ef9ed959 100644 --- a/src/pygambit/nashphc.py +++ b/src/pygambit/nashphc.py @@ -136,17 +136,13 @@ def _contingencies( yield list(profile) -def _get_strategies(support: gbt.StrategySupportProfile, player: gbt.Player) -> list[gbt.Strategy]: - return [s for s in player.strategies if s in support] - - 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 = _get_strategies(support, player) + 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) @@ -207,8 +203,7 @@ def _profile_from_support(support: gbt.StrategySupportProfile) -> gbt.MixedStrat profile = support.game.mixed_strategy_profile() for player in support.game.players: for strategy in player.strategies: - profile[strategy] = 0.0 - profile[_get_strategies(support, player)[0]] = 1.0 + profile[strategy] = 1.0 if strategy in support else 0.0 return profile @@ -254,7 +249,7 @@ def phcpack_solve(game: gbt.Game, phcpack_path: pathlib.Path | str, def main(): game = gbt.Game.parse_game(sys.stdin.read()) - phcpack_solve(game, maxregret=1.0e-6) + phcpack_solve(game, "./phc", maxregret=1.0e-6) if __name__ == "__main__": diff --git a/src/pygambit/stratspt.pxi b/src/pygambit/stratspt.pxi index 28ce63a63..dc2986c87 100644 --- a/src/pygambit/stratspt.pxi +++ b/src/pygambit/stratspt.pxi @@ -3,7 +3,7 @@ # Copyright (c) 1994-2024, The Gambit Project (http://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 @@ -24,6 +24,28 @@ 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.support).GetStrategies(self._player.player): + s = Strategy() + s.strategy = strat + yield s + + @cython.cclass class StrategySupportProfile: """A set-like object representing a subset of the strategies in game. @@ -32,20 +54,8 @@ class StrategySupportProfile: """ support = cython.declare(unique_ptr[c_StrategySupportProfile]) - def __init__(self, - game: Game, - strategies: typing.Optional[typing.Iterable[Strategy]] = None): - 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" - ) - # There's at least one strategy for each player, so this forms a valid support profile + def __init__(self, game: Game) -> None: self.support.reset(new c_StrategySupportProfile(game.game)) - if strategies is not None: - for strategy in game.strategies: - if strategy not in strategies: - deref(self.support).RemoveStrategy(cython.cast(Strategy, strategy).strategy) @property def game(self) -> Game: @@ -73,15 +83,6 @@ 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): - s = Strategy() - s.strategy = deref(self.support).GetStrategy(pl+1, index+1) - return s - 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( @@ -90,19 +91,56 @@ class StrategySupportProfile: return deref(self.support).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)): + for player in deref(self.support).GetGame().deref().GetPlayers(): + for strat in deref(self.support).GetStrategies(player): s = Strategy() - s.strategy = deref(self.support).GetStrategy(pl+1, st+1) + s.strategy = strat yield s + 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: @@ -122,13 +160,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.support).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(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 @@ -151,7 +191,7 @@ class StrategySupportProfile: """ if self.game != other.game: raise MismatchError("difference(): support profiles are defined on different games") - return StrategySupportProfile(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 @@ -174,7 +214,7 @@ class StrategySupportProfile: """ if self.game != other.game: raise MismatchError("intersection(): support profiles are defined on different games") - return StrategySupportProfile(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 @@ -197,7 +237,7 @@ class StrategySupportProfile: """ if self.game != other.game: raise MismatchError("union(): support profiles are defined on different games") - return StrategySupportProfile(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. diff --git a/src/pygambit/supports.py b/src/pygambit/supports.py index f093d4cac..a609e70db 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/solvers/enumpoly/nfgpoly.cc b/src/solvers/enumpoly/nfgpoly.cc index e725eac4b..ae14331f7 100644 --- a/src/solvers/enumpoly/nfgpoly.cc +++ b/src/solvers/enumpoly/nfgpoly.cc @@ -67,7 +67,7 @@ gPoly IndifferenceEquation(const gSpace &space, const term_order &lex, { gPoly equation(&space, &lex); - for (StrategyProfileIterator A(support, s1), B(support, s2); !A.AtEnd(); A++, B++) { + for (StrategyProfileIterator A(support, {s1}), B(support, {s2}); !A.AtEnd(); A++, B++) { gPoly term(&space, 1, &lex); for (auto player : support.GetGame()->GetPlayers()) { if (player != s1->GetPlayer()) { diff --git a/src/solvers/nashsupport/nfgsupport.cc b/src/solvers/nashsupport/nfgsupport.cc index 5c7632d49..f5260a5be 100644 --- a/src/solvers/nashsupport/nfgsupport.cc +++ b/src/solvers/nashsupport/nfgsupport.cc @@ -84,11 +84,6 @@ class CartesianRange { iterator end() { return {m_sizes, true}; } }; -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(); -} - StrategySupportProfile RestrictedGame(const Game &game, const GamePlayer &player, std::map &domainStrategies) { diff --git a/tests/test_stratprofiles.py b/tests/test_stratprofiles.py index 34785949e..1e20026bc 100644 --- a/tests/test_stratprofiles.py +++ b/tests/test_stratprofiles.py @@ -7,8 +7,8 @@ def test_remove_strategy(): game = games.read_from_file("mixed_strategy.nfg") - support_profile = game.support_profile() - strategy = support_profile[0] + support_profile = game.strategy_support_profile() + strategy = game.players["Player 1"].strategies["1"] new_profile = support_profile.remove(strategy) assert len(support_profile) == len(new_profile) + 1 assert strategy not in new_profile @@ -16,76 +16,65 @@ def test_remove_strategy(): def test_difference(): game = games.read_from_file("mixed_strategy.nfg") - support_profile = game.support_profile() - strat_list = [support_profile[0], support_profile[4]] - dif_profile = gbt.StrategySupportProfile(game, strat_list) + support_profile = game.strategy_support_profile() + strategies = [p.strategies["1"] for p in game.players] + dif_profile = game.strategy_support_profile(lambda x: x in strategies) new_profile = support_profile - dif_profile assert len(new_profile) == 3 - for strategy in strat_list: + for strategy in strategies: assert strategy not in new_profile def test_difference_error(): game = games.read_from_file("mixed_strategy.nfg") - support_profile = game.support_profile() - strat_list = [support_profile[0], support_profile[4]] - dif_profile = gbt.StrategySupportProfile(game, strat_list) + support_profile = game.strategy_support_profile() with pytest.raises(ValueError): - dif_profile - support_profile + game.strategy_support_profile(lambda x: x.label == "1") - support_profile def test_intersection(): game = games.read_from_file("mixed_strategy.nfg") - support_profile = game.support_profile() - strat_list = [support_profile[0], support_profile[2], - support_profile[4]] - fir_profile = gbt.StrategySupportProfile(game, strat_list) - sec_profile = support_profile.remove(support_profile[2]) - new_profile = fir_profile & sec_profile - assert len(new_profile) == 2 - assert new_profile <= sec_profile - assert new_profile <= fir_profile + first_profile = game.strategy_support_profile(lambda x: x.label not in ["1", "3"]) + second_profile = game.strategy_support_profile(lambda x: x.label != "3") + intersect_profile = first_profile & second_profile + assert len(intersect_profile) == 2 + assert intersect_profile <= first_profile + assert intersect_profile <= second_profile def test_intersection_error(): game = games.read_from_file("mixed_strategy.nfg") - support_profile = game.support_profile() - strat_list = [support_profile[0], support_profile[2], - support_profile[4]] - fir_profile = gbt.StrategySupportProfile(game, strat_list) - sec_profile = support_profile.remove(support_profile[4]) + first_profile = game.strategy_support_profile(lambda x: x.label not in ["1", "3"]) + second_profile = game.strategy_support_profile( + lambda x: x.player.label == "Player 1" or x.label == "1" + ) with pytest.raises(ValueError): - fir_profile & sec_profile + first_profile & second_profile def test_union(): game = games.read_from_file("mixed_strategy.nfg") - support_profile = game.support_profile() - strat_list = [support_profile[0], support_profile[2], - support_profile[4]] - fir_profile = gbt.StrategySupportProfile(game, strat_list) - sec_profile = support_profile.remove(support_profile[4]) - new_profile = fir_profile | sec_profile - assert new_profile == support_profile + first_profile = game.strategy_support_profile(lambda x: x.label not in ["1", "3"]) + second_profile = game.strategy_support_profile( + lambda x: x.player.label == "Player 1" or x.label == "1" + ) + assert (first_profile | second_profile) == game.strategy_support_profile() def test_undominated(): game = games.read_from_file("mixed_strategy.nfg") - support_profile = game.support_profile() - new_profile = support_profile - loop_profile = gbt.supports.undominated_strategies_solve(new_profile) - while loop_profile != new_profile: - new_profile = loop_profile - loop_profile = gbt.supports.undominated_strategies_solve(new_profile) - assert len(loop_profile) == 2 - assert loop_profile == gbt.StrategySupportProfile( - game, [support_profile[0], support_profile[3]] - ) + profile = gbt.supports.undominated_strategies_solve(game) + while True: + new_profile = gbt.supports.undominated_strategies_solve(profile) + if new_profile == profile: + break + profile = new_profile + assert profile == game.strategy_support_profile(lambda x: x.label == "1") def test_remove_error(): game = games.read_from_file("mixed_strategy.nfg") - support_profile = game.support_profile() - profile = support_profile.remove(support_profile[3]) + support_profile = game.strategy_support_profile() + profile = support_profile.remove(game.players["Player 2"].strategies["1"]) with pytest.raises(gbt.UndefinedOperationError): - profile.remove(profile[3]) + profile.remove(game.players["Player 2"].strategies["2"]) From 4cfd909dfce3702f92170db2171add5345958bd1 Mon Sep 17 00:00:00 2001 From: Ted Turocy Date: Thu, 31 Oct 2024 13:39:02 +0000 Subject: [PATCH 25/99] Create concept of StrategyContingencies. This creates the StrategyContingencies class, representing the set of pure-strategy profiles on a game. This then subsumes the previous StrategyProfileIterator class as the iterator of this class, re-implemented in STL iterator style. --- src/games/file.cc | 8 ++-- src/games/game.cc | 6 +-- src/games/gameagg.cc | 10 ++-- src/games/gameobject.h | 2 +- src/games/gametable.cc | 22 ++++----- src/games/stratpure.cc | 42 +++++++++-------- src/games/stratpure.h | 64 ++++++++++++------------- src/games/stratspt.cc | 15 +++--- src/games/writer.cc | 84 ++++++++++++++++----------------- src/games/writer.h | 32 ++----------- src/pygambit/util.h | 4 +- src/solvers/enumpoly/nfgpoly.cc | 6 +-- src/solvers/enumpure/enumpure.h | 6 +-- src/solvers/gtracer/gtracer.cc | 9 ++-- src/tools/convert/convert.cc | 11 +---- 15 files changed, 145 insertions(+), 176 deletions(-) diff --git a/src/games/file.cc b/src/games/file.cc index 149c00344..2d3e9823f 100644 --- a/src/games/file.cc +++ b/src/games/file.cc @@ -415,10 +415,10 @@ void ParseOutcomeBody(GameFileLexer &p_parser, Game &p_nfg) { ReadOutcomeList(p_parser, p_nfg); StrategySupportProfile profile(p_nfg); - for (StrategyProfileIterator iter(profile); !iter.AtEnd(); iter++) { + for (auto iter : StrategyContingencies(profile)) { p_parser.ExpectCurrentToken(TOKEN_NUMBER, "outcome index"); if (int outcomeId = std::stoi(p_parser.GetLastText())) { - (*iter)->SetOutcome(p_nfg->GetOutcome(outcomeId)); + iter->SetOutcome(p_nfg->GetOutcome(outcomeId)); } p_parser.GetNextToken(); } @@ -427,10 +427,10 @@ void ParseOutcomeBody(GameFileLexer &p_parser, Game &p_nfg) void ParsePayoffBody(GameFileLexer &p_parser, Game &p_nfg) { StrategySupportProfile profile(p_nfg); - for (StrategyProfileIterator iter(profile); !iter.AtEnd(); iter++) { + for (auto iter : StrategyContingencies(profile)) { for (auto player : p_nfg->GetPlayers()) { p_parser.ExpectCurrentToken(TOKEN_NUMBER, "numerical payoff"); - (*iter)->GetOutcome()->SetPayoff(player, Number(p_parser.GetLastText())); + iter->GetOutcome()->SetPayoff(player, Number(p_parser.GetLastText())); p_parser.GetNextToken(); } } diff --git a/src/games/game.cc b/src/games/game.cc index 408805169..b4ad82b97 100644 --- a/src/games/game.cc +++ b/src/games/game.cc @@ -293,12 +293,12 @@ void GameRep::WriteNfgFile(std::ostream &p_file) const p_file << "}" << std::endl; p_file << std::quoted(GetComment()) << std::endl << std::endl; - for (StrategyProfileIterator iter(StrategySupportProfile(Game(const_cast(this)))); - !iter.AtEnd(); iter++) { + for (auto iter : + StrategyContingencies(StrategySupportProfile(Game(const_cast(this))))) { p_file << FormatList( players, [&iter](const GamePlayer &p) { - return lexical_cast((*iter)->GetPayoff(p)); + return lexical_cast(iter->GetPayoff(p)); }, false, false) << std::endl; diff --git a/src/games/gameagg.cc b/src/games/gameagg.cc index 73d019ee1..494a145d2 100644 --- a/src/games/gameagg.cc +++ b/src/games/gameagg.cc @@ -264,18 +264,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); + sum += profile->GetPayoff(pl); } - 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); + newsum += iter->GetPayoff(pl); } if (newsum != sum) { return false; diff --git a/src/games/gameobject.h b/src/games/gameobject.h index d7584d978..b5001d18d 100644 --- a/src/games/gameobject.h +++ b/src/games/gameobject.h @@ -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 9ca347972..9d4c5d3b6 100644 --- a/src/games/gametable.cc +++ b/src/games/gametable.cc @@ -318,25 +318,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); + sum += profile->GetPayoff(pl); } - 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); + newsum += iter->GetPayoff(pl); } - if (newsum != sum) { return false; } } - return true; } @@ -466,22 +462,22 @@ void GameTableRep::RebuildTable() for (int i = 1; i <= newResults.Length(); newResults[i++] = 0) ; - for (StrategyProfileIterator iter(StrategySupportProfile(const_cast(this))); - !iter.AtEnd(); iter++) { + for (auto iter : + StrategyContingencies(StrategySupportProfile(const_cast(this)))) { long newindex = 1L; for (int pl = 1; pl <= m_players.Length(); pl++) { - if (iter.m_profile->GetStrategy(pl)->m_offset < 0) { + if (iter->GetStrategy(pl)->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(pl)->m_number - 1) * offsets[pl]; } } if (newindex >= 1) { - newResults[newindex] = m_results[iter.m_profile->GetIndex()]; + newResults[newindex] = m_results[iter->GetIndex()]; } } diff --git a/src/games/stratpure.cc b/src/games/stratpure.cc index 29758e1a9..13a6b7a85 100644 --- a/src/games/stratpure.cc +++ b/src/games/stratpure.cc @@ -104,45 +104,49 @@ MixedStrategyProfile PureStrategyProfileRep::ToMixedStrategyProfile() } //=========================================================================== -// class StrategyProfileIterator +// class StrategyContingencies //=========================================================================== -//--------------------------------------------------------------------------- -// Lifecycle -//--------------------------------------------------------------------------- - -StrategyProfileIterator::StrategyProfileIterator(const StrategySupportProfile &p_support, - const std::vector &p_frozen) - : m_support(p_support), m_profile(p_support.GetGame()->NewPureStrategyProfile()), - m_frozen(p_frozen) +StrategyContingencies::StrategyContingencies(const StrategySupportProfile &p_support, + const std::vector &p_frozen) + : m_support(p_support), m_frozen(p_frozen) { - for (auto strategy : m_frozen) { - m_profile->SetStrategy(strategy); - } 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); - m_currentStrat[player] = m_support.GetStrategies(player).begin(); - m_profile->SetStrategy(*m_currentStrat[player]); } } } -void StrategyProfileIterator::operator++() +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 (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]); + } +} + +StrategyContingencies::iterator &StrategyContingencies::iterator::operator++() { - for (auto player : m_unfrozen) { + for (auto player : m_cont->m_unfrozen) { ++m_currentStrat[player]; - if (m_currentStrat[player] != m_support.GetStrategies(player).end()) { + if (m_currentStrat[player] != m_cont->m_support.GetStrategies(player).end()) { m_profile->SetStrategy(*m_currentStrat[player]); - return; + return *this; } - m_currentStrat[player] = m_support.GetStrategies(player).begin(); + 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 ef2c0bf25..99577ef19 100644 --- a/src/games/stratpure.h +++ b/src/games/stratpure.h @@ -127,45 +127,45 @@ class PureStrategyProfile { explicit operator PureStrategyProfileRep *() 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{false}; StrategySupportProfile m_support; - std::map m_currentStrat; - PureStrategyProfile m_profile; std::vector m_unfrozen; std::vector m_frozen; public: - /// @name Lifecycle - //@{ - /// Construct a new iterator on the support - explicit StrategyProfileIterator(const StrategySupportProfile &, - const std::vector &p_frozen = {}); - //@} + 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 f0f956b9d..190e0d8f8 100644 --- a/src/games/stratspt.cc +++ b/src/games/stratspt.cc @@ -99,11 +99,11 @@ void StrategySupportProfile::WriteNfgFile(std::ostream &p_file) const p_file << "}" << std::endl; p_file << std::quoted(m_nfg->GetComment()) << std::endl << std::endl; - for (StrategyProfileIterator iter(*this); !iter.AtEnd(); iter++) { + for (auto iter : StrategyContingencies(*this)) { p_file << FormatList( players, [&iter](const GamePlayer &p) { - return lexical_cast((*iter)->GetPayoff(p)); + return lexical_cast(iter->GetPayoff(p)); }, false, false) << std::endl; @@ -145,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)) { + Rational ap = iter->GetStrategyValue(s); + Rational bp = iter->GetStrategyValue(t); if (p_strict && ap <= bp) { return false; } @@ -276,11 +276,12 @@ StrategySupportProfile StrategySupportProfile::Undominated(bool p_strict, bool p 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); } diff --git a/src/games/writer.cc b/src/games/writer.cc index e5f317f6b..588570a47 100644 --- a/src/games/writer.cc +++ b/src/games/writer.cc @@ -23,28 +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_game->GetPlayer(p_rowPlayer)->GetStrategies().front(), - p_game->GetPlayer(p_colPlayer)->GetStrategies().front()}); - !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 += "
"; } } @@ -52,21 +51,21 @@ 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 (int st = 1; st <= p_colPlayer->NumStrategies(); st++) { 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 (int st1 = 1; st1 <= p_rowPlayer->NumStrategies(); st1++) { + PureStrategyProfile profile = iter; + profile->SetStrategy(p_rowPlayer->GetStrategy(st1)); theHtml += ""; theHtml += ""; - for (int st2 = 1; st2 <= p_game->GetPlayer(p_colPlayer)->NumStrategies(); st2++) { - profile->SetStrategy(p_game->GetPlayer(p_colPlayer)->GetStrategy(st2)); + for (int st2 = 1; st2 <= p_colPlayer->NumStrategies(); st2++) { + profile->SetStrategy(p_colPlayer->GetStrategy(st2)); theHtml += "
"; - theHtml += p_game->GetPlayer(p_colPlayer)->GetStrategy(st)->GetLabel(); + theHtml += p_colPlayer->GetStrategy(st)->GetLabel(); theHtml += "
"; - theHtml += p_game->GetPlayer(p_rowPlayer)->GetStrategy(st1)->GetLabel(); + theHtml += p_rowPlayer->GetStrategy(st1)->GetLabel(); theHtml += ""; for (int pl = 1; pl <= p_game->NumPlayers(); pl++) { try { @@ -95,35 +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_game->GetPlayer(p_rowPlayer)->GetStrategies().front(), - p_game->GetPlayer(p_colPlayer)->GetStrategies().front()}); - !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 += lexical_cast(p_rowPlayer->NumStrategies()); theHtml += "}{"; - theHtml += lexical_cast(p_game->GetPlayer(p_colPlayer)->NumStrategies()); + theHtml += lexical_cast(p_colPlayer->NumStrategies()); 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 += "]"; @@ -131,21 +129,21 @@ 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 (int st = 1; st <= p_colPlayer->NumStrategies(); st++) { + theHtml += p_colPlayer->GetStrategy(st)->GetLabel(); + if (st < p_colPlayer->NumStrategies()) { 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 (int st1 = 1; st1 <= p_rowPlayer->NumStrategies(); st1++) { + PureStrategyProfile profile = iter; + profile->SetStrategy(p_rowPlayer->GetStrategy(st1)); + theHtml += p_rowPlayer->GetStrategy(st1)->GetLabel(); theHtml += " & "; - for (int st2 = 1; st2 <= p_game->GetPlayer(p_colPlayer)->NumStrategies(); st2++) { - profile->SetStrategy(p_game->GetPlayer(p_colPlayer)->GetStrategy(st2)); + for (int st2 = 1; st2 <= p_colPlayer->NumStrategies(); st2++) { + profile->SetStrategy(p_colPlayer->GetStrategy(st2)); theHtml += " $"; for (int pl = 1; pl <= p_game->NumPlayers(); pl++) { try { @@ -164,11 +162,11 @@ std::string LaTeXGameWriter::Write(const Game &p_game, int p_rowPlayer, int p_co } } theHtml += "$ "; - if (st2 < p_game->GetPlayer(p_colPlayer)->NumStrategies()) { + if (st2 < p_colPlayer->NumStrategies()) { theHtml += " & "; } } - if (st1 < p_game->GetPlayer(p_rowPlayer)->NumStrategies()) { + if (st1 < p_colPlayer->NumStrategies()) { theHtml += "\\\\\n"; } } @@ -178,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 a502eb2a2..19bfd361f 100644 --- a/src/games/writer.h +++ b/src/games/writer.h @@ -79,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/pygambit/util.h b/src/pygambit/util.h index 447b9c8e9..76b350734 100644 --- a/src/pygambit/util.h +++ b/src/pygambit/util.h @@ -53,10 +53,10 @@ Game ParseGame(char *s) std::string WriteGame(const Game &p_game, const std::string &p_format) { if (p_format == "html") { - return HTMLGameWriter().Write(p_game); + return WriteHTMLFile(p_game, p_game->GetPlayer(1), p_game->GetPlayer(2)); } else if (p_format == "sgame") { - return LaTeXGameWriter().Write(p_game); + return WriteLaTeXFile(p_game, p_game->GetPlayer(1), p_game->GetPlayer(2)); } else if (p_format == "native" || p_format == "nfg" || p_format == "efg") { std::ostringstream f; diff --git a/src/solvers/enumpoly/nfgpoly.cc b/src/solvers/enumpoly/nfgpoly.cc index ae14331f7..8ff23a99a 100644 --- a/src/solvers/enumpoly/nfgpoly.cc +++ b/src/solvers/enumpoly/nfgpoly.cc @@ -67,14 +67,14 @@ gPoly IndifferenceEquation(const gSpace &space, const term_order &lex, { gPoly equation(&space, &lex); - for (StrategyProfileIterator A(support, {s1}), B(support, {s2}); !A.AtEnd(); A++, B++) { + for (auto iter : StrategyContingencies(support, {s1})) { gPoly term(&space, 1, &lex); for (auto player : support.GetGame()->GetPlayers()) { if (player != s1->GetPlayer()) { - term *= strategy_poly.at((*A)->GetStrategy(player)); + term *= strategy_poly.at(iter->GetStrategy(player)); } } - term *= (*A)->GetPayoff(s1->GetPlayer()) - (*B)->GetPayoff(s1->GetPlayer()); + term *= iter->GetStrategyValue(s1) - iter->GetStrategyValue(s2); equation += term; } return equation; diff --git a/src/solvers/enumpure/enumpure.h b/src/solvers/enumpure/enumpure.h index 83bf17e7f..9e7a3797c 100644 --- a/src/solvers/enumpure/enumpure.h +++ b/src/solvers/enumpure/enumpure.h @@ -51,9 +51,9 @@ inline List> EnumPureStrategySolver::Solve(const "Computing equilibria of games with imperfect recall is not supported."); } List> solutions; - for (StrategyProfileIterator citer(p_game); !citer.AtEnd(); citer++) { - if ((*citer)->IsNash()) { - MixedStrategyProfile profile = (*citer)->ToMixedStrategyProfile(); + for (auto citer : StrategyContingencies(p_game)) { + if (citer->IsNash()) { + MixedStrategyProfile profile = citer->ToMixedStrategyProfile(); m_onEquilibrium->Render(profile); solutions.push_back(profile); } diff --git a/src/solvers/gtracer/gtracer.cc b/src/solvers/gtracer/gtracer.cc index 1c8e3ea52..265dcd92d 100644 --- a/src/solvers/gtracer/gtracer.cc +++ b/src/solvers/gtracer/gtracer.cc @@ -45,13 +45,12 @@ std::shared_ptr BuildGame(const Game &p_game, bool p_scaled) std::shared_ptr A(new nfgame(actions)); std::vector profile(players.size()); - for (StrategyProfileIterator iter(p_game); !iter.AtEnd(); iter++) { - std::transform(players.cbegin(), players.cend(), profile.begin(), [iter](const GamePlayer &p) { - return (*iter)->GetStrategy(p)->GetNumber() - 1; - }); + for (auto iter : StrategyContingencies(p_game)) { + std::transform(players.cbegin(), players.cend(), profile.begin(), + [iter](const GamePlayer &p) { return iter->GetStrategy(p)->GetNumber() - 1; }); for (auto player : players) { A->setPurePayoff(player->GetNumber() - 1, profile, - scale * ((*iter)->GetPayoff(player) - minPay)); + scale * (iter->GetPayoff(player) - minPay)); } } return A; diff --git a/src/tools/convert/convert.cc b/src/tools/convert/convert.cc index 2bdf6144b..484f59596 100644 --- a/src/tools/convert/convert.cc +++ b/src/tools/convert/convert.cc @@ -28,8 +28,6 @@ #include "gambit.h" #include "games/writer.h" -void WriteOsborneFile(std::ostream &, const Gambit::Game &, int, int); - void PrintBanner(std::ostream &p_stream) { p_stream << "Convert games among various file formats\n"; @@ -126,9 +124,6 @@ int main(int argc, char *argv[]) try { Gambit::Game game = Gambit::ReadGame(*input_stream); - game->WriteNfgFile(std::cout); - exit(0); - if (rowPlayer < 1 || rowPlayer > game->NumPlayers()) { std::cerr << argv[0] << ": Player " << rowPlayer << " does not exist.\n"; return 1; @@ -139,12 +134,10 @@ int main(int argc, char *argv[]) } if (format == "html") { - Gambit::HTMLGameWriter writer; - std::cout << writer.Write(game, rowPlayer, colPlayer); + std::cout << WriteHTMLFile(game, game->GetPlayer(rowPlayer), game->GetPlayer(colPlayer)); } else { - Gambit::LaTeXGameWriter writer; - std::cout << writer.Write(game, rowPlayer, colPlayer); + std::cout << WriteLaTeXFile(game, game->GetPlayer(rowPlayer), game->GetPlayer(colPlayer)); } return 0; } From c9a7f8c6a7a8193ce1aa21fdf8e5338288be635a Mon Sep 17 00:00:00 2001 From: Ted Turocy Date: Thu, 7 Nov 2024 11:07:37 +0000 Subject: [PATCH 26/99] Use a map to point to strategy probabilities in MixedStrategyProfileRep. --- src/games/game.cc | 7 +++---- src/games/game.h | 7 ++----- src/games/gameagg.cc | 11 +++-------- src/games/gamebagg.cc | 11 +++-------- src/games/gametable.cc | 7 ------- src/games/gametree.cc | 6 ------ src/games/stratmixed.h | 9 +++------ 7 files changed, 14 insertions(+), 44 deletions(-) diff --git a/src/games/game.cc b/src/games/game.cc index b4ad82b97..000d316f5 100644 --- a/src/games/game.cc +++ b/src/games/game.cc @@ -312,17 +312,16 @@ void GameRep::WriteNfgFile(std::ostream &p_file) const template MixedStrategyProfileRep::MixedStrategyProfileRep(const StrategySupportProfile &p_support) : m_probs(p_support.MixedProfileLength()), m_support(p_support), - m_profileIndex(p_support.GetGame()->MixedProfileLength()), m_gameversion(p_support.GetGame()->GetVersion()) { - int index = 1, stnum = 1; + int index = 1; for (auto player : p_support.GetGame()->GetPlayers()) { for (auto strategy : player->GetStrategies()) { if (p_support.Contains(strategy)) { - m_profileIndex[stnum++] = index++; + m_profileIndex[strategy] = index++; } else { - m_profileIndex[stnum++] = -1; + m_profileIndex[strategy] = -1; } } } diff --git a/src/games/game.h b/src/games/game.h index 1fb664e5f..289ab78b1 100644 --- a/src/games/game.h +++ b/src/games/game.h @@ -239,7 +239,7 @@ class GameStrategyRep : public GameObject { template friend class MixedBehaviorProfile; private: - int m_number, m_id; + int m_number; GamePlayerRep *m_player; long m_offset; std::string m_label; @@ -248,8 +248,7 @@ class GameStrategyRep : public GameObject { /// @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) : m_number(0), m_player(p_player), m_offset(0L) { } //@} @@ -266,8 +265,6 @@ 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(); diff --git a/src/games/gameagg.cc b/src/games/gameagg.cc index 494a145d2..1ef4b753a 100644 --- a/src/games/gameagg.cc +++ b/src/games/gameagg.cc @@ -103,7 +103,7 @@ template T AGGMixedStrategyProfileRep::GetPayoff(int pl) const for (int i = 0; i < g.aggPtr->getNumPlayers(); ++i) { for (int j = 0; j < g.aggPtr->getNumActions(i); ++j) { GameStrategy strategy = this->m_support.GetGame()->GetPlayer(i + 1)->GetStrategy(j + 1); - int ind = this->m_profileIndex[strategy->GetId()]; + int ind = this->m_profileIndex.at(strategy); s[g.aggPtr->firstAction(i) + j] = (ind == -1) ? (T)0 : this->m_probs[ind]; } } @@ -125,7 +125,7 @@ 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_profileIndex[strategy->GetId()]; + const int &ind = this->m_profileIndex.at(strategy); s[g.aggPtr->firstAction(i) + j] = (ind == -1) ? (T)0 : this->m_probs[ind]; } } @@ -161,7 +161,7 @@ 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_profileIndex[strategy->GetId()]; + int ind = this->m_profileIndex.at(strategy); s[g.aggPtr->firstAction(i) + j] = (ind == -1) ? (T)0 : this->m_probs[ind]; } } @@ -185,11 +185,6 @@ GameAGGRep::GameAGGRep(std::shared_ptr p_aggPtr) : aggPtr(p_aggPtr) 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++) - ; - } } Game GameAGGRep::Copy() const diff --git a/src/games/gamebagg.cc b/src/games/gamebagg.cc index 7a1deefb2..3b931d7a9 100644 --- a/src/games/gamebagg.cc +++ b/src/games/gamebagg.cc @@ -115,7 +115,7 @@ template T BAGGMixedStrategyProfileRep::GetPayoff(int pl) const GameStrategy strategy = this->m_support.GetGame() ->GetPlayer(g.baggPtr->typeOffset[i] + tp + 1) ->GetStrategy(j + 1); - int ind = this->m_profileIndex[strategy->GetId()]; + int ind = this->m_profileIndex.at(strategy); s.at(offs) = (ind == -1) ? (T)0 : this->m_probs[ind]; } } @@ -146,7 +146,7 @@ T BAGGMixedStrategyProfileRep::GetPayoffDeriv(int pl, const GameStrategy &ps) GameStrategy strategy = this->m_support.GetGame() ->GetPlayer(g.baggPtr->typeOffset[i] + tp + 1) ->GetStrategy(j + 1); - int ind = this->m_profileIndex[strategy->GetId()]; + int ind = this->m_profileIndex.at(strategy); s.at(g.baggPtr->firstAction(i, tp) + j) = (ind == -1) ? Rational(0) : this->m_probs[ind]; } } @@ -192,7 +192,7 @@ T BAGGMixedStrategyProfileRep::GetPayoffDeriv(int pl, const GameStrategy &ps1 GameStrategy strategy = this->m_support.GetGame() ->GetPlayer(g.baggPtr->typeOffset[i] + tp + 1) ->GetStrategy(j + 1); - int ind = this->m_profileIndex[strategy->GetId()]; + 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]); } @@ -223,11 +223,6 @@ GameBAGGRep::GameBAGGRep(std::shared_ptr _baggPtr) } } } - 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 diff --git a/src/games/gametable.cc b/src/games/gametable.cc index 9d4c5d3b6..4c0e12520 100644 --- a/src/games/gametable.cc +++ b/src/games/gametable.cc @@ -498,13 +498,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/gametree.cc b/src/games/gametree.cc index a54b1bf5f..d887e8a88 100644 --- a/src/games/gametree.cc +++ b/src/games/gametree.cc @@ -907,12 +907,6 @@ void GameTreeRep::BuildComputedValues() m_players[pl]->MakeReducedStrats(m_root, nullptr); } - 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_computedValues = true; } diff --git a/src/games/stratmixed.h b/src/games/stratmixed.h index 1b6f010a5..6bf55fa7e 100644 --- a/src/games/stratmixed.h +++ b/src/games/stratmixed.h @@ -34,7 +34,7 @@ template class MixedStrategyProfileRep { Vector m_probs; StrategySupportProfile m_support; /// The index into the strategy profile for a strategy (-1 if not in support) - Array m_profileIndex; + std::map m_profileIndex; unsigned int m_gameversion; explicit MixedStrategyProfileRep(const StrategySupportProfile &); @@ -46,13 +46,10 @@ template class MixedStrategyProfileRep { /// Returns the probability the strategy is played const T &operator[](const GameStrategy &p_strategy) const { - return m_probs[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) - { - return m_probs[m_profileIndex[p_strategy->GetId()]]; - } + T &operator[](const GameStrategy &p_strategy) { return m_probs[m_profileIndex.at(p_strategy)]; } virtual T GetPayoff(int pl) const = 0; virtual T GetPayoffDeriv(int pl, const GameStrategy &) const = 0; From 25742d2c9ade743ed908b89dbe818e9a877b88b5 Mon Sep 17 00:00:00 2001 From: Ted Turocy Date: Fri, 8 Nov 2024 10:49:07 +0000 Subject: [PATCH 27/99] Separate builds for MacOS 13 and 14, 14 uses the M1-based runner. --- .github/workflows/tools.yml | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tools.yml b/.github/workflows/tools.yml index 7bb5cf882..ffb5d3afb 100644 --- a/.github/workflows/tools.yml +++ b/.github/workflows/tools.yml @@ -40,8 +40,8 @@ jobs: make sudo make install - macos: - runs-on: macos-latest + 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 @@ -56,7 +56,26 @@ jobs: - run: make osx-dmg - uses: actions/upload-artifact@v4 with: - name: artifact-osx + name: artifact-osx-13 + path: "*.dmg" + + macos-14: + runs-on: macos-14 + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name + steps: + - uses: actions/checkout@v4 + - name: Install dependencies + run: brew install automake autoconf wxwidgets + - run: aclocal + - run: automake --add-missing + - run: autoconf + - run: ./configure + - run: make + - run: sudo make install + - run: make osx-dmg + - uses: actions/upload-artifact@v4 + with: + name: artifact-osx-14 path: "*.dmg" windows: From a86c21c421fd611f4722f0493e8cbd132bee712a Mon Sep 17 00:00:00 2001 From: Ted Turocy Date: Thu, 7 Nov 2024 12:17:15 +0000 Subject: [PATCH 28/99] Refactor and modernise behavior support and behavior contingency iteration. This addresses several items of techincal debt with respect to working with behavior supports and iterating over behavior contingencies: * Behavior contingencies is now an iterable class, and combines the functionality of several variations previously scattered in different locations * Accessing elements by numeric indices has been removed in favour of consistently using game objects as appropriate * The internal representation of behavior supports has been migrated to use STL containers * Computation and elimination of dominated actions in extensive games is now always conditional on reaching the information set, and the implementation has been simplified. --- src/core/rational.cc | 2 - src/core/rational.h | 2 + src/games/behavmixed.cc | 41 ++- src/games/behavmixed.h | 8 +- src/games/behavpure.cc | 173 ++++----- src/games/behavpure.h | 111 +++--- src/games/behavspt.cc | 497 ++++++-------------------- src/games/behavspt.h | 75 ++-- src/games/gametree.cc | 2 +- src/games/nash.cc | 5 +- src/games/stratspt.h | 2 +- src/gui/gamedoc.cc | 9 +- src/solvers/enumpoly/efgpoly.cc | 14 +- src/solvers/enumpure/enumpure.h | 6 +- src/solvers/nashsupport/efgsupport.cc | 101 ++---- 15 files changed, 346 insertions(+), 702 deletions(-) diff --git a/src/core/rational.cc b/src/core/rational.cc index ddad5a189..74b6e2897 100644 --- a/src/core/rational.cc +++ b/src/core/rational.cc @@ -439,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 f8f6295b3..de5c1f857 100644 --- a/src/core/rational.h +++ b/src/core/rational.h @@ -116,6 +116,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 diff --git a/src/games/behavmixed.cc b/src/games/behavmixed.cc index 06b742d8e..f7f7f3e12 100644 --- a/src/games/behavmixed.cc +++ b/src/games/behavmixed.cc @@ -38,6 +38,14 @@ MixedBehaviorProfile::MixedBehaviorProfile(const Game &p_game) : m_probs(p_game->NumActions()), 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(); } @@ -46,6 +54,19 @@ MixedBehaviorProfile::MixedBehaviorProfile(const BehaviorSupportProfile &p_su : m_probs(p_support.NumActions()), 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(); } @@ -84,7 +105,7 @@ void MixedBehaviorProfile::RealizationProbs(const MixedStrategyProfile &mp } } else if (GetSupport().Contains(node->GetInfoset()->GetAction(i))) { - int num_actions = GetSupport().NumActions(node->GetInfoset()); + int num_actions = GetSupport().GetActions(node->GetInfoset()).size(); prob = T(1) / T(num_actions); } else { @@ -109,6 +130,15 @@ MixedBehaviorProfile::MixedBehaviorProfile(const MixedStrategyProfile &p_p : m_probs(p_profile.GetGame()->NumActions()), m_support(p_profile.GetGame()), m_gameversion(p_profile.GetGame()->GetVersion()) { + 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++; + } + } + } + static_cast &>(m_probs) = T(0); GameTreeNodeRep *root = @@ -172,8 +202,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; } @@ -195,7 +225,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()); } } } @@ -322,8 +352,7 @@ template T MixedBehaviorProfile::GetActionProb(const GameAction &ac 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)]; } } diff --git a/src/games/behavmixed.h b/src/games/behavmixed.h index d72394fe9..c54aa2253 100644 --- a/src/games/behavmixed.h +++ b/src/games/behavmixed.h @@ -35,6 +35,8 @@ template class MixedBehaviorProfile { protected: DVector 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 @@ -107,14 +109,12 @@ template class MixedBehaviorProfile { 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]; } diff --git a/src/games/behavpure.cc b/src/games/behavpure.cc index 81d52cdbc..f84458298 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 += static_cast(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,14 +68,14 @@ 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 @@ -100,10 +84,10 @@ template Rational PureBehaviorProfile::GetPayoff(const GameAction &) const; bool PureBehaviorProfile::IsAgentNash() const { - for (auto player : m_efg->GetPlayers()) { + for (const auto &player : m_efg->GetPlayers()) { auto current = GetPayoff(player); - for (auto infoset : player->GetInfosets()) { - for (auto action : infoset->GetActions()) { + for (const auto &infoset : player->GetInfosets()) { + for (const auto &action : infoset->GetActions()) { if (GetPayoff(action) > current) { return false; } @@ -117,110 +101,75 @@ MixedBehaviorProfile PureBehaviorProfile::ToMixedBehaviorProfile() con { 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 7587dd046..3d0c18fdc 100644 --- a/src/games/behavpure.h +++ b/src/games/behavpure.h @@ -33,31 +33,32 @@ 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 &); + //@} + + 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; @@ -69,60 +70,70 @@ class PureBehaviorProfile { //@} }; -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 7672ee114..0c4d238ce 100644 --- a/src/games/behavspt.cc +++ b/src/games/behavspt.cc @@ -30,38 +30,24 @@ 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; + 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); } //======================================================================== @@ -71,9 +57,10 @@ bool BehaviorSupportProfile::operator==(const BehaviorSupportProfile &p_support) 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); + for (const auto &player : m_efg->GetPlayers()) { + for (const auto &infoset : player->GetInfosets()) { + answer(player->GetNumber(), infoset->GetNumber()) = + static_cast(m_actions.at(infoset).size()); } } @@ -83,78 +70,39 @@ PVector BehaviorSupportProfile::NumActions() const 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 +void BehaviorSupportProfile::AddAction(const GameAction &p_action) { - 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; + 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)); + } } } -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; + 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)); } - else if (actions[act]->GetNumber() > s->GetNumber()) { - actions.Insert(s, act); - break; - } - 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 @@ -169,9 +117,9 @@ 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; @@ -179,7 +127,7 @@ void ReachableInfosets(const BehaviorSupportProfile &p_support, const GameNode & 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); } @@ -193,112 +141,8 @@ 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++() -{ - 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 +bool BehaviorSupportProfile::Dominates(const GameAction &a, const GameAction &b, + bool p_strict) const { GameInfoset infoset = a->GetInfoset(); if (infoset != b->GetInfoset()) { @@ -306,194 +150,77 @@ bool BehaviorSupportProfile::Dominates(const GameAction &a, const GameAction &b, } 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; - } + 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; } } - } - - 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); - } - } - - 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; - } + return p_strict || thesign > 0; } -bool SomeElementDominates(const BehaviorSupportProfile &S, const Array &array, - const GameAction &a, const bool strong, const bool conditional) +bool BehaviorSupportProfile::IsDominated(const GameAction &p_action, const bool p_strict) const { - for (int i = 1; i <= array.Length(); i++) { - if (array[i] != a) { - if (S.Dominates(array[i], a, strong, conditional)) { - return true; - } - } - } - return false; -} - -bool BehaviorSupportProfile::IsDominated(const GameAction &a, bool strong, bool conditional) 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; -} - -bool ElimDominatedForPlayer(const BehaviorSupportProfile &S, BehaviorSupportProfile &T, - const int pl, int &cumiset, const bool strong, const bool conditional) -{ - 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; + return result; } -// Utilities -bool BehaviorSupportProfile::HasActiveMembers(int pl, int iset) const +bool BehaviorSupportProfile::HasReachableMembers(const GameInfoset &p_infoset) 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) @@ -502,15 +229,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)); } } } @@ -520,42 +245,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 a9329590d..3717283d5 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,35 @@ 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 @@ -121,14 +134,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/gametree.cc b/src/games/gametree.cc index d887e8a88..bf738f993 100644 --- a/src/games/gametree.cc +++ b/src/games/gametree.cc @@ -1290,7 +1290,7 @@ Rational TreePureStrategyProfileRep::GetPayoff(int pl) const } } } - return behav.GetPayoff(pl); + return behav.GetPayoff(m_nfg->GetPlayer(pl)); } Rational TreePureStrategyProfileRep::GetStrategyValue(const GameStrategy &p_strategy) const diff --git a/src/games/nash.cc b/src/games/nash.cc index f349e53cb..179a68d51 100644 --- a/src/games/nash.cc +++ b/src/games/nash.cc @@ -338,9 +338,8 @@ void SubgameBehavSolver::SolveSubgames(const Game &p_game, const DVector & 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)]; + for (const auto &action : subsupport.GetActions(subinfoset)) { + solns.back()(pl, id, action->GetNumber()) = sol[solno][action]; } break; } diff --git a/src/games/stratspt.h b/src/games/stratspt.h index bf9e6c1c4..4776a798c 100644 --- a/src/games/stratspt.h +++ b/src/games/stratspt.h @@ -52,7 +52,7 @@ class StrategySupportProfile { public: using const_iterator = std::vector::const_iterator; - Support() : m_profile(0), m_player(0) {} + Support() : m_profile(nullptr), m_player(nullptr) {} Support(const StrategySupportProfile *p_profile, GamePlayer p_player) : m_profile(p_profile), m_player(p_player) { diff --git a/src/gui/gamedoc.cc b/src/gui/gamedoc.cc index fda6c898f..9a7350ef4 100644 --- a/src/gui/gamedoc.cc +++ b/src/gui/gamedoc.cc @@ -79,14 +79,7 @@ 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); + Gambit::BehaviorSupportProfile newSupport = m_supports[m_current]->Undominated(m_strict); if (newSupport != *m_supports[m_current]) { m_supports.push_back(new Gambit::BehaviorSupportProfile(newSupport)); diff --git a/src/solvers/enumpoly/efgpoly.cc b/src/solvers/enumpoly/efgpoly.cc index 0e348d91a..03d5537e5 100644 --- a/src/solvers/enumpoly/efgpoly.cc +++ b/src/solvers/enumpoly/efgpoly.cc @@ -67,8 +67,7 @@ gPoly BuildSequenceVariable(ProblemData &p_data, const GameSequence &p_s if (!p_sequence->action) { return {&p_data.Space, 1, &p_data.Lex}; } - if (p_data.sfg.GetSupport().GetIndex(p_sequence->action) < - p_data.sfg.GetSupport().NumActions(p_sequence->GetInfoset())) { + if (p_sequence->action != p_data.sfg.GetSupport().GetActions(p_sequence->GetInfoset()).back()) { return {&p_data.Space, var.at(p_sequence), 1, &p_data.Lex}; } @@ -92,7 +91,7 @@ ProblemData::ProblemData(const BehaviorSupportProfile &p_support) { for (auto sequence : sfg.GetSequences()) { if (sequence->action && - (p_support.GetIndex(sequence->action) < p_support.NumActions(sequence->GetInfoset()))) { + (sequence->action != p_support.GetActions(sequence->GetInfoset()).back())) { var[sequence] = var.size() + 1; } } @@ -138,10 +137,11 @@ void IndifferenceEquations(ProblemData &p_data, gPolyList &p_equations) void LastActionProbPositiveInequalities(ProblemData &p_data, gPolyList &p_equations) { for (auto sequence : p_data.sfg.GetSequences()) { - int action_number = - (sequence->action) ? p_data.sfg.GetSupport().GetIndex(sequence->action) : 0; - if (action_number > 1 && - action_number == p_data.sfg.GetSupport().NumActions(sequence->action->GetInfoset())) { + if (!sequence->action) { + continue; + } + const auto &actions = p_data.sfg.GetSupport().GetActions(sequence->action->GetInfoset()); + if (actions.size() > 1 && sequence->action == actions.back()) { p_equations += p_data.variables.at(sequence); } } diff --git a/src/solvers/enumpure/enumpure.h b/src/solvers/enumpure/enumpure.h index 9e7a3797c..e43cf3da3 100644 --- a/src/solvers/enumpure/enumpure.h +++ b/src/solvers/enumpure/enumpure.h @@ -89,9 +89,9 @@ inline List> EnumPureAgentSolver::Solve(const Gam { List> solutions; BehaviorSupportProfile support(p_game); - for (BehaviorProfileIterator citer(p_game); !citer.AtEnd(); citer++) { - if ((*citer).IsAgentNash()) { - MixedBehaviorProfile profile = (*citer).ToMixedBehaviorProfile(); + for (auto citer : BehaviorContingencies(BehaviorSupportProfile(p_game))) { + if (citer.IsAgentNash()) { + MixedBehaviorProfile profile = citer.ToMixedBehaviorProfile(); m_onEquilibrium->Render(profile); solutions.push_back(profile); } diff --git a/src/solvers/nashsupport/efgsupport.cc b/src/solvers/nashsupport/efgsupport.cc index 273722788..8b797d39c 100644 --- a/src/solvers/nashsupport/efgsupport.cc +++ b/src/solvers/nashsupport/efgsupport.cc @@ -26,70 +26,7 @@ namespace { // to keep the recursive function private using namespace Gambit; -class ActionCursor { -protected: - BehaviorSupportProfile m_support; - int pl{1}, iset{1}, act{1}; - -public: - // Lifecycle - explicit ActionCursor(const BehaviorSupportProfile &S); - ~ActionCursor() = default; - - // Operators - ActionCursor &operator=(const ActionCursor &) = default; - - // Manipulation - bool GoToNext(); - - // State - GameAction GetAction() const { return m_support.GetAction(pl, iset, act); } -}; - -ActionCursor::ActionCursor(const BehaviorSupportProfile &p_support) : m_support(p_support) -{ - Game efg = p_support.GetGame(); - - // Make sure that (pl, iset) points to a valid information set. - // It is permitted to have a game where a player has no information sets. - for (; pl <= efg->NumPlayers(); pl++) { - if (efg->GetPlayer(pl)->NumInfosets() > 0) { - for (iset = 1; iset <= efg->GetPlayer(pl)->NumInfosets(); iset++) { - if (m_support.NumActions(pl, iset) > 0) { - return; - } - } - } - } -} - -bool ActionCursor::GoToNext() -{ - if (act != m_support.NumActions(pl, iset)) { - act++; - return true; - } - - int temppl = pl, tempiset = iset + 1; - while (temppl <= m_support.GetGame()->NumPlayers()) { - while (tempiset <= m_support.GetGame()->GetPlayer(temppl)->NumInfosets()) { - if (m_support.NumActions(temppl, tempiset) > 0) { - pl = temppl; - iset = tempiset; - act = 1; - return true; - } - else { - tempiset++; - } - } - tempiset = 1; - temppl++; - } - return false; -} - -bool HasActiveActionsAtActiveInfosetsAndNoOthers(const BehaviorSupportProfile &p_support) +bool AllActionsReachable(const BehaviorSupportProfile &p_support) { for (auto player : p_support.GetGame()->GetPlayers()) { for (auto infoset : player->GetInfosets()) { @@ -102,27 +39,29 @@ bool HasActiveActionsAtActiveInfosetsAndNoOthers(const BehaviorSupportProfile &p } void PossibleNashBehaviorSupports(const BehaviorSupportProfile &p_support, - const ActionCursor &p_cursor, + const std::list::iterator &p_cursor, + const std::list::iterator &p_end, std::list &p_list) { - ActionCursor copy(p_cursor); - if (!copy.GoToNext()) { - if (HasActiveActionsAtActiveInfosetsAndNoOthers(p_support)) { + auto copy = std::next(p_cursor); + if (copy == p_end) { + if (AllActionsReachable(p_support)) { p_list.push_back(p_support); } BehaviorSupportProfile copySupport(p_support); - copySupport.RemoveAction(p_cursor.GetAction()); - if (HasActiveActionsAtActiveInfosetsAndNoOthers(copySupport)) { + copySupport.RemoveAction(*p_cursor); + if (AllActionsReachable(copySupport)) { p_list.push_back(copySupport); } - return; } - PossibleNashBehaviorSupports(p_support, copy, p_list); + else { + PossibleNashBehaviorSupports(p_support, copy, p_end, p_list); - BehaviorSupportProfile copySupport(p_support); - copySupport.RemoveAction(p_cursor.GetAction()); - PossibleNashBehaviorSupports(copySupport, copy, p_list); + BehaviorSupportProfile copySupport(p_support); + copySupport.RemoveAction(*p_cursor); + PossibleNashBehaviorSupports(copySupport, copy, p_end, p_list); + } } } // namespace @@ -136,9 +75,17 @@ std::shared_ptr PossibleNashBehaviorSupports(const Game &p_game) { BehaviorSupportProfile support(p_game); - ActionCursor cursor(support); auto result = std::make_shared(); - PossibleNashBehaviorSupports(support, cursor, result->m_supports); + std::list actions; + for (const auto &player : p_game->GetPlayers()) { + for (const auto &infoset : player->GetInfosets()) { + for (const auto &action : infoset->GetActions()) { + actions.push_back(action); + } + } + } + + PossibleNashBehaviorSupports(support, actions.begin(), actions.end(), result->m_supports); return result; } From bca6be33dee999832837fddd7b60fe6ccf475ad0 Mon Sep 17 00:00:00 2001 From: Ted Turocy Date: Wed, 20 Nov 2024 11:34:31 +0000 Subject: [PATCH 29/99] Refactor solve-by-subgames implementation. This re-writes the implementation of solving by subgames. This feature is not yet widely visible. However, this accomplishes two intermediate goals: * Makes the implementation much more understandable; * The implementation uses standard C++ containers instead of Gambit's. There are plans for better ways to accomplish partial solution of games which will subsume this. Meanwhile, this will make the current implementation more accessible and maintainable, and will permit it to be made visible as appropriate to users. --- src/games/nash.cc | 226 ++++++++++++++++++++++------------------------ src/games/nash.h | 6 +- 2 files changed, 110 insertions(+), 122 deletions(-) diff --git a/src/games/nash.cc b/src/games/nash.cc index 179a68d51..23c891900 100644 --- a/src/games/nash.cc +++ b/src/games/nash.cc @@ -224,15 +224,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,128 +247,113 @@ 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; + const std::map &GetProfile() const { return profile; } + const std::map &GetNodeValues() const { return node_values; } - SolveSubgames(p_game, p_templateSolution, subroots[i], subsolns, subvalues); - - 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()) { + 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); + GameOutcome outcome = p_subroot->GetOutcome(); + const auto &subplayers = p_profile.GetGame()->GetPlayers(); + auto subplayer = subplayers.begin(); + for (const auto &player : p_subroot->GetGame()->GetPlayers()) { + T value = p_profile.GetPayoff(*subplayer); + if (outcome) { + value += static_cast(outcome->GetPayoff(*subplayer)); + } + 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> +SubgameBehavSolver::SolveSubgames(const GameNode &p_root, + const std::map &p_infosetMap) const +{ + std::list> subsolutions = {{{}, {}}}; + for (const auto &subroot : ChildSubgames(p_root)) { + std::list> combined_solutions; + for (const auto &solution : SolveSubgames(subroot, p_infosetMap)) { + for (const auto &subsolution : subsolutions) { + combined_solutions.push_back(subsolution.Combine(solution)); + } } + if (combined_solutions.empty()) { + return {}; + } + subsolutions = combined_solutions; + } - 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 + std::list> solutions; + for (auto subsolution : subsolutions) { + for (auto [subroot, outcome] : subsolution.GetNodeValues()) { + subroot->SetOutcome(outcome); + } + // This prevents double-counting of outcomes at roots of subgames. + // By convention, we will just put the payoffs in the parent subgame. + Game subgame = p_root->CopySubgame(); subgame->GetRoot()->SetOutcome(nullptr); - BehaviorSupportProfile subsupport(subgame); - List> sol = m_solver->Solve(p_game); - - if (sol.empty()) { - solns = List>(); - return; + for (const auto &solution : m_solver->Solve(subgame)) { + solutions.push_back(subsolution.Update(p_root, solution, p_infosetMap)); } + } - // 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); - - for (int j = 1; j <= player->NumInfosets(); j++) { - if (subinfoset->GetLabel() == player->GetInfoset(j)->GetLabel()) { - int id = atoi(subinfoset->GetLabel().c_str()); - for (const auto &action : subsupport.GetActions(subinfoset)) { - solns.back()(pl, id, action->GetNumber()) = sol[solno][action]; - } - break; - } - } - } - } - - 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->DeleteTree(); + 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 @@ -375,25 +361,25 @@ List> SubgameBehavSolver::Solve(const Game &p_game) c { Game efg = p_game->GetRoot()->CopySubgame(); - 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); + List> solutions; + for (const auto &result : results) { + solutions.push_back(BuildProfile(p_game, result)); + this->m_onEquilibrium->Render(solutions.back()); } return solutions; } diff --git a/src/games/nash.h b/src/games/nash.h index 7bab8ce06..d7852ee13 100644 --- a/src/games/nash.h +++ b/src/games/nash.h @@ -209,6 +209,8 @@ template class BehavViaStrategySolver : public BehavSolver { std::shared_ptr> m_solver; }; +template class SubgameSolution; + template class SubgameBehavSolver : public BehavSolver { public: explicit SubgameBehavSolver( @@ -222,8 +224,8 @@ template class SubgameBehavSolver : public BehavSolver { std::shared_ptr> m_solver; private: - void SolveSubgames(const Game &p_game, const DVector &p_templateSolution, const GameNode &n, - List> &solns, List &values) const; + std::list> SolveSubgames(const GameNode &, + const std::map &) const; }; // From c52494540d332af3d8909e12d6d848dee86d92a7 Mon Sep 17 00:00:00 2001 From: Ted Turocy Date: Thu, 21 Nov 2024 12:28:40 +0000 Subject: [PATCH 30/99] Internal improvements in logit-solve for extensive games. This accomplishes two refactorings: * Expressing the equations using game objects instead of integer indices; * Removing the dependency on DVector, instead using a mapping from actions to elements in the vector. --- src/solvers/logit/efglogit.cc | 105 ++++++++++++--------------------- src/solvers/logit/logbehav.h | 27 ++++----- src/solvers/logit/logbehav.imp | 19 ++++-- 3 files changed, 63 insertions(+), 88 deletions(-) diff --git a/src/solvers/logit/efglogit.cc b/src/solvers/logit/efglogit.cc index af44846c8..1e4a51bf0 100644 --- a/src/solvers/logit/efglogit.cc +++ b/src/solvers/logit/efglogit.cc @@ -84,7 +84,7 @@ bool RegretTerminationFunction(const Game &p_game, const Vector &p_point class EquationSystem { public: explicit EquationSystem(const Game &p_game); - ~EquationSystem(); + ~EquationSystem() = default; // Compute the value of the system of equations at the specified point. void GetValue(const Vector &p_point, Vector &p_lhs) const; @@ -113,13 +113,11 @@ class EquationSystem { class SumToOneEquation : public Equation { private: Game m_game; - int m_pl, m_iset; GameInfoset m_infoset; public: - SumToOneEquation(const Game &p_game, int p_player, int p_infoset) - : m_game(p_game), m_pl(p_player), m_iset(p_infoset), - m_infoset(p_game->GetPlayer(p_player)->GetInfoset(p_infoset)) + SumToOneEquation(const Game &p_game, const GameInfoset &p_infoset) + : m_game(p_game), m_infoset(p_infoset) { } @@ -131,19 +129,18 @@ class EquationSystem { // // This class represents the equation relating the probability of - // playing action (pl,iset,act) to the probability of playing action - // (pl,iset,1) + // playing the action to the probability of playing the reference action. // class RatioEquation : public Equation { private: Game m_game; - int m_pl, m_iset, m_act; GameInfoset m_infoset; + GameAction m_action, m_refAction; public: - RatioEquation(const Game &p_game, int p_player, int p_infoset, int p_action) - : m_game(p_game), m_pl(p_player), m_iset(p_infoset), m_act(p_action), - m_infoset(p_game->GetPlayer(p_player)->GetInfoset(p_infoset)) + RatioEquation(const Game &p_game, const GameAction &p_action, const GameAction &p_refAction) + : m_game(p_game), m_infoset(p_action->GetInfoset()), m_action(p_action), + m_refAction(p_refAction) { } @@ -153,36 +150,29 @@ class EquationSystem { Vector &p_gradient) const override; }; - Array m_equations; + Array> m_equations; const Game &m_game; }; EquationSystem::EquationSystem(const Game &p_game) : m_game(p_game) { - for (int pl = 1; pl <= m_game->NumPlayers(); pl++) { - GamePlayer player = m_game->GetPlayer(pl); - for (int iset = 1; iset <= player->NumInfosets(); iset++) { - m_equations.push_back(new SumToOneEquation(m_game, pl, iset)); - for (int act = 2; act <= player->GetInfoset(iset)->NumActions(); act++) { - m_equations.push_back(new RatioEquation(m_game, pl, iset, act)); + for (const auto &player : m_game->GetPlayers()) { + for (const auto &infoset : player->GetInfosets()) { + m_equations.push_back(std::make_shared(m_game, infoset)); + const auto &actions = infoset->GetActions(); + for (auto action = std::next(actions.begin()); action != actions.end(); ++action) { + m_equations.push_back(std::make_shared(m_game, *action, actions.front())); } } } } -EquationSystem::~EquationSystem() -{ - for (int i = 1; i <= m_equations.Length(); i++) { - delete m_equations[i]; - } -} - double EquationSystem::SumToOneEquation::Value(const LogBehavProfile &p_profile, double p_lambda) const { double value = -1.0; - for (int act = 1; act <= m_infoset->NumActions(); act++) { - value += p_profile.GetProb(m_pl, m_iset, act); + for (const auto &action : m_infoset->GetActions()) { + value += p_profile.GetProb(action); } return value; } @@ -191,23 +181,13 @@ void EquationSystem::SumToOneEquation::Gradient(const LogBehavProfile &p double p_lambda, Vector &p_gradient) const { int i = 1; - for (int pl = 1; pl <= m_game->NumPlayers(); pl++) { - GamePlayer player = m_game->GetPlayer(pl); - - for (int iset = 1; iset <= player->NumInfosets(); iset++) { - GameInfoset infoset = player->GetInfoset(iset); - - for (int act = 1; act <= infoset->NumActions(); act++, i++) { - if (pl == m_pl && iset == m_iset) { - p_gradient[i] = p_profile.GetProb(pl, iset, act); - } - else { - p_gradient[i] = 0.0; - } + for (const auto &player : m_game->GetPlayers()) { + for (const auto &infoset : player->GetInfosets()) { + for (const auto &action : infoset->GetActions()) { + p_gradient[i++] = (infoset == m_infoset) ? p_profile.GetProb(action) : 0.0; } } } - // Derivative wrt lambda is zero p_gradient[i] = 0.0; } @@ -215,43 +195,36 @@ void EquationSystem::SumToOneEquation::Gradient(const LogBehavProfile &p double EquationSystem::RatioEquation::Value(const LogBehavProfile &p_profile, double p_lambda) const { - return (p_profile.GetLogProb(m_pl, m_iset, m_act) - p_profile.GetLogProb(m_pl, m_iset, 1) - - p_lambda * (p_profile.GetPayoff(m_infoset->GetAction(m_act)) - - p_profile.GetPayoff(m_infoset->GetAction(1)))); + return (p_profile.GetLogProb(m_action) - p_profile.GetLogProb(m_refAction) - + p_lambda * (p_profile.GetPayoff(m_action) - p_profile.GetPayoff(m_refAction))); } void EquationSystem::RatioEquation::Gradient(const LogBehavProfile &p_profile, double p_lambda, Vector &p_gradient) const { int i = 1; - for (int pl = 1; pl <= m_game->NumPlayers(); pl++) { - GamePlayer player = m_game->GetPlayer(pl); - for (int iset = 1; iset <= player->NumInfosets(); iset++) { - GameInfoset infoset = player->GetInfoset(iset); - for (int act = 1; act <= infoset->NumActions(); act++, i++) { - if (infoset == m_infoset) { - if (act == 1) { - p_gradient[i] = -1.0; - } - else if (act == m_act) { - p_gradient[i] = 1.0; - } - else { - p_gradient[i] = 0.0; - } + for (const auto &player : m_game->GetPlayers()) { + for (const auto &infoset : player->GetInfosets()) { + for (const auto &action : infoset->GetActions()) { + if (action == m_refAction) { + p_gradient[i] = -1.0; } - else { // infoset1 != infoset2 - p_gradient[i] = - -p_lambda * - (p_profile.DiffActionValue(m_infoset->GetAction(m_act), infoset->GetAction(act)) - - p_profile.DiffActionValue(m_infoset->GetAction(1), infoset->GetAction(act))); + else if (action == m_action) { + p_gradient[i] = 1.0; + } + else if (infoset == m_infoset) { + p_gradient[i] = 0.0; + } + else { + p_gradient[i] = -p_lambda * (p_profile.DiffActionValue(m_action, action) - + p_profile.DiffActionValue(m_refAction, action)); } + i++; } } } - p_gradient[i] = (p_profile.GetPayoff(m_infoset->GetAction(1)) - - p_profile.GetPayoff(m_infoset->GetAction(m_act))); + p_gradient[i] = (p_profile.GetPayoff(m_refAction) - p_profile.GetPayoff(m_action)); } void EquationSystem::GetValue(const Vector &p_point, Vector &p_lhs) const diff --git a/src/solvers/logit/logbehav.h b/src/solvers/logit/logbehav.h index 8da8cad55..28cc09349 100644 --- a/src/solvers/logit/logbehav.h +++ b/src/solvers/logit/logbehav.h @@ -52,7 +52,8 @@ using namespace Gambit; template class LogBehavProfile { protected: Game m_game; - DVector m_probs, m_logProbs; + Vector m_probs, m_logProbs; + std::map m_profileIndex; // structures for storing cached data mutable bool m_cacheValid; @@ -61,8 +62,6 @@ template class LogBehavProfile { mutable std::map> m_nodeValues; mutable std::map m_actionValues; - const T &ActionValue(const GameAction &act) const { return m_actionValues[act]; } - /// @name Auxiliary functions for computation of interesting values //@{ void ComputeSolutionDataPass2(const GameNode &node) const; @@ -87,24 +86,20 @@ template class LogBehavProfile { void SetProb(const GameAction &p_action, const T &p_value) { - m_probs(p_action->GetInfoset()->GetPlayer()->GetNumber(), p_action->GetInfoset()->GetNumber(), - p_action->GetNumber()) = p_value; - m_logProbs(p_action->GetInfoset()->GetPlayer()->GetNumber(), - p_action->GetInfoset()->GetNumber(), p_action->GetNumber()) = log(p_value); + m_probs[m_profileIndex.at(p_action)] = p_value; + m_logProbs[m_profileIndex.at(p_action)] = log(p_value); } + const T &GetProb(const GameAction &p_action) const { - return m_probs(p_action->GetInfoset()->GetPlayer()->GetNumber(), - p_action->GetInfoset()->GetNumber(), p_action->GetNumber()); + return m_probs[m_profileIndex.at(p_action)]; } - const T &GetLogProb(int a, int b, int c) const { return m_logProbs(a, b, c); } - const T &GetProb(int a, int b, int c) const { return m_probs(a, b, c); } - void SetProb(int a, const T &p_value) + + const T &GetLogProb(const GameAction &p_action) const { - Invalidate(); - m_logProbs[a] = log(p_value); - m_probs[a] = p_value; + return m_logProbs[m_profileIndex.at(p_action)]; } + void SetLogProb(int a, const T &p_value) { Invalidate(); @@ -121,7 +116,7 @@ template class LogBehavProfile { /// @name General data access //@{ - size_t BehaviorProfileLength() const { return m_probs.Length(); } + size_t BehaviorProfileLength() const { return m_probs.size(); } //@} /// @name Computation of interesting quantities diff --git a/src/solvers/logit/logbehav.imp b/src/solvers/logit/logbehav.imp index 4126523b0..a10bf0e13 100644 --- a/src/solvers/logit/logbehav.imp +++ b/src/solvers/logit/logbehav.imp @@ -30,10 +30,19 @@ template LogBehavProfile::LogBehavProfile(const Game &p_game) - : m_game(p_game), m_probs(m_game->NumActions()), m_logProbs(m_game->NumActions()), + : m_game(p_game), m_probs(m_game->BehavProfileLength()), m_logProbs(m_game->BehavProfileLength()), m_cacheValid(false) { - for (auto infoset : m_game->GetInfosets()) { + 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++; + } + } + } + + for (auto infoset : m_game->GetInfosets()) { if (infoset->NumActions() > 0) { T center = (T(1) / T(infoset->NumActions())); for (auto act : infoset->GetActions()) { @@ -64,8 +73,7 @@ template T LogBehavProfile::GetActionProb(const GameAction &action) return static_cast(infoset->GetActionProb(action->GetNumber())); } else { - return GetProb(action->GetInfoset()->GetPlayer()->GetNumber(), - action->GetInfoset()->GetNumber(), action->GetNumber()); + return GetProb(action); } } @@ -77,8 +85,7 @@ template T LogBehavProfile::GetLogActionProb(const GameAction &acti return log(static_cast(infoset->GetActionProb(action->GetNumber()))); } else { - return m_logProbs(action->GetInfoset()->GetPlayer()->GetNumber(), - action->GetInfoset()->GetNumber(), action->GetNumber()); + return m_logProbs[m_profileIndex.at(action)]; } } From a6fcf5a7d6fdf24abf8b0013d1691a57a60459ac Mon Sep 17 00:00:00 2001 From: Ted Turocy Date: Thu, 21 Nov 2024 13:17:19 +0000 Subject: [PATCH 31/99] Remove some unneeded or unused classes and functions. This removes the DVector container class, which is no longer needed now that we have re-implemented MixedBehaviorProfile. A follow-on from this is that we can remove the NumActions() and NumMembers() functions on games and behavior supports, as those were only providing the shapes to create the underlying DVectors. --- Makefile.am | 3 - src/core/dvector.cc | 27 -------- src/core/dvector.h | 69 -------------------- src/core/dvector.imp | 115 --------------------------------- src/games/behavmixed.cc | 21 ++---- src/games/behavmixed.h | 15 +++-- src/games/behavspt.cc | 13 ---- src/games/behavspt.h | 2 - src/games/game.h | 6 +- src/games/gameagg.h | 4 -- src/games/gamebagg.h | 4 -- src/games/gametable.h | 4 -- src/games/gametree.cc | 36 ----------- src/games/gametree.h | 4 -- src/solvers/liap/efgliap.cc | 9 ++- src/solvers/logit/logbehav.imp | 10 +-- 16 files changed, 28 insertions(+), 314 deletions(-) delete mode 100644 src/core/dvector.cc delete mode 100644 src/core/dvector.h delete mode 100644 src/core/dvector.imp diff --git a/Makefile.am b/Makefile.am index 00e65a661..bc8c65ff2 100644 --- a/Makefile.am +++ b/Makefile.am @@ -251,8 +251,6 @@ core_SOURCES = \ 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,7 +262,6 @@ core_SOURCES = \ 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 \ diff --git a/src/core/dvector.cc b/src/core/dvector.cc deleted file mode 100644 index 969622ba8..000000000 --- a/src/core/dvector.cc +++ /dev/null @@ -1,27 +0,0 @@ -// -// This file is part of Gambit -// Copyright (c) 1994-2024, The Gambit Project (http://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 449e4f8eb..000000000 --- a/src/core/dvector.h +++ /dev/null @@ -1,69 +0,0 @@ -// -// This file is part of Gambit -// Copyright (c) 1994-2024, The Gambit Project (http://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 5a62afb39..000000000 --- a/src/core/dvector.imp +++ /dev/null @@ -1,115 +0,0 @@ -// -// This file is part of Gambit -// Copyright (c) 1994-2024, The Gambit Project (http://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/games/behavmixed.cc b/src/games/behavmixed.cc index f7f7f3e12..940b5a06a 100644 --- a/src/games/behavmixed.cc +++ b/src/games/behavmixed.cc @@ -35,7 +35,7 @@ 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; @@ -51,7 +51,7 @@ MixedBehaviorProfile::MixedBehaviorProfile(const Game &p_game) 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; @@ -127,20 +127,19 @@ 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()) { 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_profileIndex[action] = index; + m_probs[index++] = static_cast(0); } } } - static_cast &>(m_probs) = T(0); - GameTreeNodeRep *root = dynamic_cast(m_support.GetGame()->GetRoot().operator->()); @@ -184,16 +183,6 @@ MixedBehaviorProfile::operator=(const MixedBehaviorProfile &p_profile) 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 //======================================================================== diff --git a/src/games/behavmixed.h b/src/games/behavmixed.h index c54aa2253..9ec3135a3 100644 --- a/src/games/behavmixed.h +++ b/src/games/behavmixed.h @@ -33,7 +33,7 @@ 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; @@ -101,11 +101,14 @@ 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 { diff --git a/src/games/behavspt.cc b/src/games/behavspt.cc index 0c4d238ce..582f31f5c 100644 --- a/src/games/behavspt.cc +++ b/src/games/behavspt.cc @@ -54,19 +54,6 @@ BehaviorSupportProfile::BehaviorSupportProfile(const Game &p_efg) : m_efg(p_efg) // BehaviorSupportProfile: General information //======================================================================== -PVector BehaviorSupportProfile::NumActions() const -{ - PVector answer(m_efg->NumInfosets()); - for (const auto &player : m_efg->GetPlayers()) { - for (const auto &infoset : player->GetInfosets()) { - answer(player->GetNumber(), infoset->GetNumber()) = - static_cast(m_actions.at(infoset).size()); - } - } - - return answer; -} - size_t BehaviorSupportProfile::BehaviorProfileLength() const { size_t answer = 0; diff --git a/src/games/behavspt.h b/src/games/behavspt.h index 3717283d5..4bad1748c 100644 --- a/src/games/behavspt.h +++ b/src/games/behavspt.h @@ -97,8 +97,6 @@ class BehaviorSupportProfile { /// Returns the game on which the support is defined. Game GetGame() const { return m_efg; } - /// 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; diff --git a/src/games/game.h b/src/games/game.h index 289ab78b1..a0cc39898 100644 --- a/src/games/game.h +++ b/src/games/game.h @@ -28,7 +28,7 @@ #include #include -#include "core/dvector.h" +#include "core/pvector.h" #include "number.h" #include "gameobject.h" @@ -485,10 +485,6 @@ class GameRep : public BaseGameRep { /// @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 diff --git a/src/games/gameagg.h b/src/games/gameagg.h index b1b10204d..3a770e6d1 100644 --- a/src/games/gameagg.h +++ b/src/games/gameagg.h @@ -54,10 +54,6 @@ 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 diff --git a/src/games/gamebagg.h b/src/games/gamebagg.h index aa6cbf475..ec59af05a 100644 --- a/src/games/gamebagg.h +++ b/src/games/gamebagg.h @@ -54,10 +54,6 @@ 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 diff --git a/src/games/gametable.h b/src/games/gametable.h index 7bd8fc2cc..629f7da0a 100644 --- a/src/games/gametable.h +++ b/src/games/gametable.h @@ -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(); } //@} diff --git a/src/games/gametree.cc b/src/games/gametree.cc index bf738f993..e1e564f22 100644 --- a/src/games/gametree.cc +++ b/src/games/gametree.cc @@ -982,42 +982,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; diff --git a/src/games/gametree.h b/src/games/gametree.h index 5d0e66f63..46a1df6d3 100644 --- a/src/games/gametree.h +++ b/src/games/gametree.h @@ -277,10 +277,6 @@ class GameTreeRep : public GameExplicitRep { /// @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; //@} diff --git a/src/solvers/liap/efgliap.cc b/src/solvers/liap/efgliap.cc index a2d22d9bc..827910cad 100644 --- a/src/solvers/liap/efgliap.cc +++ b/src/solvers/liap/efgliap.cc @@ -45,6 +45,12 @@ class AgentLyapunovFunction : public FunctionOnSimplices { else { m_scale = 1.0 / m_scale; } + + for (const auto &player : m_game->GetPlayers()) { + for (const auto &infoset : player->GetInfosets()) { + m_shape.push_back(infoset->NumActions()); + } + } } ~AgentLyapunovFunction() override = default; @@ -54,6 +60,7 @@ class AgentLyapunovFunction : public FunctionOnSimplices { private: Game m_game; mutable MixedBehaviorProfile m_profile; + Array m_shape; double m_scale, m_penalty{100.0}; double Value(const Vector &x) const override; @@ -116,7 +123,7 @@ bool AgentLyapunovFunction::Gradient(const Vector &x, Vector &gr m_profile[i] += DELTA; grad[i] = value / (2.0 * DELTA); } - Project(grad, static_cast &>(m_game->NumActions())); + Project(grad, m_shape); return true; } diff --git a/src/solvers/logit/logbehav.imp b/src/solvers/logit/logbehav.imp index a10bf0e13..6683887ce 100644 --- a/src/solvers/logit/logbehav.imp +++ b/src/solvers/logit/logbehav.imp @@ -30,19 +30,19 @@ template LogBehavProfile::LogBehavProfile(const Game &p_game) - : m_game(p_game), m_probs(m_game->BehavProfileLength()), m_logProbs(m_game->BehavProfileLength()), - m_cacheValid(false) + : m_game(p_game), m_probs(m_game->BehavProfileLength()), + m_logProbs(m_game->BehavProfileLength()), m_cacheValid(false) { int index = 1; for (const auto &player : p_game->GetPlayers()) { - for (const auto &infoset: player->GetInfosets()) { - for (const auto &action: infoset->GetActions()) { + for (const auto &infoset : player->GetInfosets()) { + for (const auto &action : infoset->GetActions()) { m_profileIndex[action] = index++; } } } - for (auto infoset : m_game->GetInfosets()) { + for (auto infoset : m_game->GetInfosets()) { if (infoset->NumActions() > 0) { T center = (T(1) / T(infoset->NumActions())); for (auto act : infoset->GetActions()) { From 3eb95581120d5f3faeaf0f6e3cc8cd9ab202930c Mon Sep 17 00:00:00 2001 From: rahulsavani Date: Tue, 19 Nov 2024 08:42:26 +0000 Subject: [PATCH 32/99] Better comments nim.efg/nim7.efg and entry in samples.rst --- contrib/games/nim.efg | 2 +- contrib/games/nim7.efg | 2 +- doc/samples.rst | 11 ++++++++--- 3 files changed, 10 insertions(+), 5 deletions(-) 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/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: From c74d40c75983af6b4894efa1b0d385956f84de1c Mon Sep 17 00:00:00 2001 From: Ted Turocy Date: Fri, 22 Nov 2024 10:55:06 +0000 Subject: [PATCH 33/99] Move simplified PVector to simplicial subdivision The class PVector is now only used in the implementation of simplicial subdivision. This moves a streamlined version of that class to be located as part of the algorithm ipmplementation. --- Makefile.am | 3 - src/core/pvector.cc | 28 ---- src/core/pvector.h | 85 ---------- src/core/pvector.imp | 297 --------------------------------- src/games/game.h | 1 - src/solvers/simpdiv/simpdiv.cc | 82 ++++++--- src/solvers/simpdiv/simpdiv.h | 24 +-- 7 files changed, 71 insertions(+), 449 deletions(-) delete mode 100644 src/core/pvector.cc delete mode 100644 src/core/pvector.h delete mode 100644 src/core/pvector.imp diff --git a/Makefile.am b/Makefile.am index bc8c65ff2..70cac768d 100644 --- a/Makefile.am +++ b/Makefile.am @@ -249,8 +249,6 @@ core_SOURCES = \ src/core/list.h \ src/core/vector.h \ src/core/vector.imp \ - src/core/pvector.h \ - src/core/pvector.imp \ src/core/recarray.h \ src/core/matrix.h \ src/core/matrix.imp \ @@ -261,7 +259,6 @@ core_SOURCES = \ src/core/rational.cc \ src/core/rational.h \ src/core/vector.cc \ - src/core/pvector.cc \ src/core/matrix.cc \ src/core/sqmatrix.cc \ src/core/function.cc \ diff --git a/src/core/pvector.cc b/src/core/pvector.cc deleted file mode 100644 index deacf3bb5..000000000 --- a/src/core/pvector.cc +++ /dev/null @@ -1,28 +0,0 @@ -// -// This file is part of Gambit -// Copyright (c) 1994-2024, The Gambit Project (http://www.gambit-project.org) -// -// FILE: src/libgambit/pvector.cc -// Instantiation of 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 "pvector.imp" - -template class Gambit::PVector; -template class Gambit::PVector; -template class Gambit::PVector; diff --git a/src/core/pvector.h b/src/core/pvector.h deleted file mode 100644 index 3721db8a8..000000000 --- a/src/core/pvector.h +++ /dev/null @@ -1,85 +0,0 @@ -// -// This file is part of Gambit -// Copyright (c) 1994-2024, The Gambit Project (http://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 80d4afdbb..000000000 --- a/src/core/pvector.imp +++ /dev/null @@ -1,297 +0,0 @@ -// -// This file is part of Gambit -// Copyright (c) 1994-2024, The Gambit Project (http://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/games/game.h b/src/games/game.h index a0cc39898..a6eb14294 100644 --- a/src/games/game.h +++ b/src/games/game.h @@ -28,7 +28,6 @@ #include #include -#include "core/pvector.h" #include "number.h" #include "gameobject.h" diff --git a/src/solvers/simpdiv/simpdiv.cc b/src/solvers/simpdiv/simpdiv.cc index db94a4327..7ab03b767 100644 --- a/src/solvers/simpdiv/simpdiv.cc +++ b/src/solvers/simpdiv/simpdiv.cc @@ -20,11 +20,51 @@ // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // +#include #include "gambit.h" #include "solvers/simpdiv/simpdiv.h" -namespace Gambit { -namespace Nash { +namespace Gambit::Nash { + +template class PVector { +private: + Vector m_values; + Array m_offsets; + Array m_shape; + +public: + explicit PVector(const Array &p_shape) + : m_values(std::accumulate(p_shape.begin(), p_shape.end(), 0)), m_offsets(p_shape.size()), + m_shape(p_shape) + { + for (int index = 0, i = 1; i <= m_shape.size(); i++) { + m_offsets[i] = index; + index += m_shape[i]; + } + } + PVector(const PVector &v) = default; + ~PVector() = default; + + size_t size() const { return m_values.size(); } + T &operator[](const int a) { return m_values[a]; } + T &operator()(const int a, const int b) { return m_values[m_offsets[a] + b]; } + const T &operator()(const int a, const int b) const { return m_values[m_offsets[a] + b]; } + + PVector &operator=(const PVector &v) = default; + PVector &operator=(const Vector &v) + { + m_values = v; + return *this; + } + PVector &operator=(const T &c) + { + m_values = c; + return *this; + } + + const Array &GetShape() const { return m_shape; } + explicit operator const Vector &() const { return m_values; } +}; //------------------------------------------------------------------------- // NashSimpdivStrategySolver: Private member functions @@ -41,7 +81,7 @@ class NashSimpdivStrategySolver::State { int t{0}, ibar{1}; Rational d, pay, maxz, bestz; - State(int p_leashLength) : m_leashLength(p_leashLength), bestz(1.0e30) {} + explicit State(int p_leashLength) : m_leashLength(p_leashLength), bestz(1.0e30) {} Rational getlabel(MixedStrategyProfile &yy, Array &, PVector &); /* Check whether the distance p_dist is "too far" given the leash length, if set. */ @@ -67,7 +107,7 @@ Rational NashSimpdivStrategySolver::Simplex(MixedStrategyProfile &y, RectArray labels(y.MixedProfileLength(), 2), pi(y.MixedProfileLength(), 2); PVector U(nstrats), TT(nstrats); PVector ab(nstrats), besty(nstrats), v(nstrats); - for (int i = 1; i <= v.Length(); i++) { + for (int i = 1; i <= v.size(); i++) { v[i] = y[i]; } besty = static_cast &>(y); @@ -303,12 +343,10 @@ Rational NashSimpdivStrategySolver::Simplex(MixedStrategyProfile &y, } void NashSimpdivStrategySolver::update(State &state, RectArray &pi, RectArray &labels, - PVector &ab, const PVector &U, int j, - int i) const + PVector &ab, const PVector &U, int j, int i) { - int jj, hh, k, f; + int jj, hh, k, f = 1; - f = 1; if (i >= 2 && i <= state.t) { pi.SwitchRows(i, i - 1); state.ibar = i; @@ -319,14 +357,14 @@ void NashSimpdivStrategySolver::update(State &state, RectArray &pi, RectArr jj = pi(1, 1); hh = pi(1, 2); if (jj == j) { - k = get_c(jj, hh, ab.Lengths()[jj], U); + k = get_c(jj, hh, ab.GetShape()[jj], U); while (f) { if (k == hh) { f = 0; } ab(j, k) += Rational(1); k++; - if (k > ab.Lengths()[jj]) { + if (k > ab.GetShape()[jj]) { k = 1; } } @@ -339,14 +377,14 @@ void NashSimpdivStrategySolver::update(State &state, RectArray &pi, RectArr jj = pi(state.t, 1); hh = pi(state.t, 2); if (jj == j) { - k = get_c(jj, hh, ab.Lengths()[jj], U); + k = get_c(jj, hh, ab.GetShape()[jj], U); while (f) { if (k == hh) { f = 0; } ab(j, k) -= Rational(1); k++; - if (k > ab.Lengths()[jj]) { + if (k > ab.GetShape()[jj]) { k = 1; } } @@ -355,12 +393,12 @@ void NashSimpdivStrategySolver::update(State &state, RectArray &pi, RectArr } } -void NashSimpdivStrategySolver::getY(State &state, MixedStrategyProfile &x, +void NashSimpdivStrategySolver::getY(const State &state, MixedStrategyProfile &x, PVector &v, const PVector &U, const PVector &TT, const PVector &ab, - const RectArray &pi, int k) const + const RectArray &pi, int k) { - x = v; + x = static_cast &>(v); for (size_t j = 1; j <= x.GetGame()->NumPlayers(); j++) { GamePlayer player = x.GetGame()->GetPlayer(j); for (size_t h = 1; h <= player->GetStrategies().size(); h++) { @@ -376,9 +414,8 @@ void NashSimpdivStrategySolver::getY(State &state, MixedStrategyProfile &x, - const RectArray &pi, const PVector &U, - int i) const +void NashSimpdivStrategySolver::getnexty(const State &state, MixedStrategyProfile &x, + const RectArray &pi, const PVector &U, int i) { int j = pi(i, 1); GamePlayer player = x.GetGame()->GetPlayer(j); @@ -388,7 +425,7 @@ void NashSimpdivStrategySolver::getnexty(State &state, MixedStrategyProfileGetStrategies()[hh]] -= state.d; } -int NashSimpdivStrategySolver::get_b(int j, int h, int nstrats, const PVector &U) const +int NashSimpdivStrategySolver::get_b(int j, int h, int nstrats, const PVector &U) { int hh = (h > 1) ? h - 1 : nstrats; while (U(j, hh)) { @@ -400,7 +437,7 @@ int NashSimpdivStrategySolver::get_b(int j, int h, int nstrats, const PVector &U) const +int NashSimpdivStrategySolver::get_c(int j, int h, int nstrats, const PVector &U) { int hh = get_b(j, h, nstrats, U) + 1; return (hh > nstrats) ? 1 : hh; @@ -464,7 +501,7 @@ NashSimpdivStrategySolver::Solve(const MixedStrategyProfile &p_start) throw UndefinedException( "Computing equilibria of games with imperfect recall is not supported."); } - Rational d(Integer(1), find_lcd((const Vector &)p_start)); + Rational d(Integer(1), find_lcd(static_cast &>(p_start))); Rational scale = p_start.GetGame()->GetMaxPayoff() - p_start.GetGame()->GetMinPayoff(); MixedStrategyProfile y(p_start); @@ -512,5 +549,4 @@ List> NashSimpdivStrategySolver::Solve(const Game return Solve(start); } -} // namespace Nash -} // end namespace Gambit +} // end namespace Gambit::Nash diff --git a/src/solvers/simpdiv/simpdiv.h b/src/solvers/simpdiv/simpdiv.h index 322b1094c..a9fe9c53f 100644 --- a/src/solvers/simpdiv/simpdiv.h +++ b/src/solvers/simpdiv/simpdiv.h @@ -25,9 +25,9 @@ #include "games/nash.h" -namespace Gambit { -namespace Nash { +namespace Gambit::Nash { +template class PVector; /// /// This is a simplicial subdivision algorithm with restart, for finding /// mixed strategy solutions to general finite n-person games. It is based on @@ -57,14 +57,15 @@ class NashSimpdivStrategySolver : public StrategySolver { class State; Rational Simplex(MixedStrategyProfile &, const Rational &d) const; - void update(State &, RectArray &, RectArray &, PVector &, - const PVector &, int j, int i) const; - void getY(State &, MixedStrategyProfile &x, PVector &, const PVector &, - const PVector &, const PVector &, const RectArray &, int k) const; - void getnexty(State &, MixedStrategyProfile &x, const RectArray &, - const PVector &, int i) const; - int get_c(int j, int h, int nstrats, const PVector &) const; - int get_b(int j, int h, int nstrats, const PVector &) const; + static void update(State &, RectArray &, RectArray &, PVector &, + const PVector &, int j, int i); + static void getY(const State &, MixedStrategyProfile &x, PVector &, + const PVector &, const PVector &, const PVector &, + const RectArray &, int k); + static void getnexty(const State &, MixedStrategyProfile &x, const RectArray &, + const PVector &, int i); + static int get_c(int j, int h, int nstrats, const PVector &); + static int get_b(int j, int h, int nstrats, const PVector &); }; inline List> @@ -75,7 +76,6 @@ SimpdivStrategySolve(const MixedStrategyProfile &p_start, return NashSimpdivStrategySolver(p_gridResize, p_leashLength, p_maxregret).Solve(p_start); } -} // namespace Nash -} // end namespace Gambit +} // end namespace Gambit::Nash #endif // GAMBIT_NASH_SIMPDIV_H From f06caa39fedd5796e690689310742f7c83e8e287 Mon Sep 17 00:00:00 2001 From: Ted Turocy Date: Mon, 2 Dec 2024 12:52:29 +0000 Subject: [PATCH 34/99] Modernisation of LU decomposition Various modernisation updates to LU decomposition: * Removal of unused or redundant code * Use of STL containers and idioms * Simplification of data structure --- src/solvers/linalg/ludecomp.cc | 14 +- src/solvers/linalg/ludecomp.h | 81 +++----- src/solvers/linalg/ludecomp.imp | 325 +++++++++++--------------------- src/solvers/linalg/tableau.h | 2 +- 4 files changed, 136 insertions(+), 286 deletions(-) diff --git a/src/solvers/linalg/ludecomp.cc b/src/solvers/linalg/ludecomp.cc index f7778bc6f..5fc36559a 100644 --- a/src/solvers/linalg/ludecomp.cc +++ b/src/solvers/linalg/ludecomp.cc @@ -22,16 +22,8 @@ #include "ludecomp.imp" -namespace Gambit { +namespace Gambit::linalg { -namespace linalg { +template class LUDecomposition; -template class EtaMatrix; -template class LUdecomp; - -template class EtaMatrix; -template class LUdecomp; - -} // namespace linalg - -} // end namespace Gambit +} // end namespace Gambit::linalg diff --git a/src/solvers/linalg/ludecomp.h b/src/solvers/linalg/ludecomp.h index 9c43e00e1..6c2449aa3 100644 --- a/src/solvers/linalg/ludecomp.h +++ b/src/solvers/linalg/ludecomp.h @@ -26,56 +26,30 @@ #include "gambit.h" #include "basis.h" -namespace Gambit { - -namespace linalg { +namespace Gambit::linalg { template class Tableau; -// --------------------------------------------------------------------------- -// Class EtaMatrix -// --------------------------------------------------------------------------- - -template class EtaMatrix { -public: - int col; - Vector etadata; - - EtaMatrix(int c, Vector &v) : col(c), etadata(v) {} - - // required for list class - bool operator==(const EtaMatrix &) const; - bool operator!=(const EtaMatrix &) const; -}; - -// --------------------------------------------------------------------------- -// Class LUdecomp -// --------------------------------------------------------------------------- - -template class LUdecomp { +template class LUDecomposition { private: + struct EtaMatrix { + int col; + Vector etadata; + }; + Tableau &tab; Basis &basis; - List> L; - List> U; - List> E; - List P; - - Vector scratch1; // scratch vectors so we don't reallocate them - Vector scratch2; // everytime we do something. + std::list U; + std::list E; + std::vector> L; int refactor_number; int iterations; int total_operations; - const LUdecomp *parent; - int copycount; - - // don't use this copy constructor - LUdecomp(const LUdecomp &a); - // don't use the equals operator, use the Copy function instead - LUdecomp &operator=(const LUdecomp &); + const LUDecomposition *parent; + mutable int copycount; public: class BadPivot : public Exception { @@ -93,24 +67,29 @@ template class LUdecomp { // Constructors, Destructor // ------------------------ + LUDecomposition(const LUDecomposition &) = delete; + // copy constructor // note: Copying will fail an assertion if you try to update or delete // the original before the copy has been deleted, refactored // Or set to something else. - LUdecomp(const LUdecomp &, Tableau &); + LUDecomposition(const LUDecomposition &, Tableau &); // Decompose given matrix - explicit LUdecomp(Tableau &, int rfac = 0); + explicit LUDecomposition(Tableau &, int rfac = 0); // Destructor - ~LUdecomp(); + ~LUDecomposition(); + + // don't use the equals operator, use the Copy function instead + LUDecomposition &operator=(const LUDecomposition &) = delete; // -------------------- // Public Members // -------------------- // copies the LUdecomp given (expect for the basis &). - void Copy(const LUdecomp &, Tableau &); + void Copy(const LUDecomposition &, Tableau &); // replace (update) the column given with the vector given. void update(int, int matcol); // matcol is the column number in the matrix @@ -126,19 +105,14 @@ template class LUdecomp { // set number of etamatrices added before refactoring; // if number is set to zero, refactoring is done automatically. - // if number is < 0, no refactoring is done; - void SetRefactor(int); - - //------------------- - // Private Members - //------------------- + // if number is < 0, no refactoring is done + void SetRefactor(int a) { refactor_number = a; } private: void FactorBasis(); void GaussElem(Matrix &, int, int); - bool CheckBasis(); bool RefactorCheck(); void BTransE(Vector &) const; @@ -148,9 +122,8 @@ template class LUdecomp { void LPd_Trans(Vector &) const; void yLP_Trans(Vector &) const; - void VectorEtaSolve(const Vector &v, const EtaMatrix &, Vector &y) const; - - void EtaVectorSolve(const Vector &v, const EtaMatrix &, Vector &d) const; + void VectorEtaSolve(const EtaMatrix &, Vector &y) const; + void EtaVectorSolve(const EtaMatrix &, Vector &d) const; void yLP_mult(const Vector &y, int j, Vector &) const; @@ -158,8 +131,6 @@ template class LUdecomp { }; // end of class LUdecomp -} // namespace linalg - -} // end namespace Gambit +} // end namespace Gambit::linalg #endif // LUDECOMP_H diff --git a/src/solvers/linalg/ludecomp.imp b/src/solvers/linalg/ludecomp.imp index b27aa6b41..928cadcdf 100644 --- a/src/solvers/linalg/ludecomp.imp +++ b/src/solvers/linalg/ludecomp.imp @@ -24,96 +24,62 @@ #include "ludecomp.h" #include "tableau.h" -namespace Gambit { - -namespace linalg { - -// --------------------------------------------------------------------------- -// Class EtaMatrix -// --------------------------------------------------------------------------- - -template bool EtaMatrix::operator==(const EtaMatrix &a) const -{ - return (col == a.col && etadata == a.etadata); -} - -template bool EtaMatrix::operator!=(const EtaMatrix &a) const -{ - return (col != a.col || etadata != a.etadata); -} - -// --------------------------------------------------------------------------- -// Class LUdecomp -// --------------------------------------------------------------------------- -// ------------------------- -// C-tors, D-tor, Operators -// ------------------------- - -// copy constructor +namespace Gambit::linalg { template -LUdecomp::LUdecomp(const LUdecomp &a, Tableau &t) - : tab(t), basis(t.GetBasis()), scratch1(basis.First(), basis.Last()), - scratch2(basis.First(), basis.Last()), refactor_number(a.refactor_number), - iterations(a.iterations), total_operations(a.total_operations), parent(&a), copycount(0) +LUDecomposition::LUDecomposition(const LUDecomposition &a, Tableau &t) + : tab(t), basis(t.GetBasis()), refactor_number(a.refactor_number), iterations(a.iterations), + total_operations(a.total_operations), parent(&a), copycount(0) { - ((LUdecomp &)*parent).copycount++; + parent->copycount++; } -// Decomposes given matrix - template -LUdecomp::LUdecomp(Tableau &t, int rfac /* = 0 */) - : tab(t), basis(t.GetBasis()), scratch1(basis.First(), basis.Last()), - scratch2(basis.First(), basis.Last()), refactor_number(rfac), iterations(0), parent(nullptr), +LUDecomposition::LUDecomposition(Tableau &t, int rfac /* = 0 */) + : tab(t), basis(t.GetBasis()), refactor_number(rfac), iterations(0), parent(nullptr), copycount(0) { - int m = basis.Last() - basis.First() + 1; + auto m = basis.Last() - basis.First() + 1; total_operations = (m - 1) * m * (2 * m - 1) / 6; } -// Destructor -template LUdecomp::~LUdecomp() +template LUDecomposition::~LUDecomposition() { if (parent != nullptr) { - ((LUdecomp &)*parent).copycount--; + parent->copycount--; } - // if(copycount != 0) throw BadCount(); } // ------------------------- // Public Members // ------------------------- -// use this to copy ludecomps.... -template void LUdecomp::Copy(const LUdecomp &orig, Tableau &t) +template void LUDecomposition::Copy(const LUDecomposition &orig, Tableau &t) { if (this != &orig) { if (parent != nullptr) { - ((LUdecomp &)*parent).copycount--; + parent->copycount--; } tab = t; basis = t.GetBasis(); - L = List>(); - P = List(); - E = List>(); - U = List>(); + L.clear(); + E.clear(); + U.clear(); refactor_number = orig.refactor_number; iterations = orig.iterations; total_operations = orig.total_operations; parent = &orig; copycount = 0; - ((LUdecomp &)*parent).copycount++; + parent->copycount++; } } -template void LUdecomp::update(int col, int matcol) +template void LUDecomposition::update(int col, int matcol) { - if (copycount != 0) { throw BadCount(); } @@ -125,24 +91,23 @@ template void LUdecomp::update(int col, int matcol) refactor(); } else { - tab.GetColumn(matcol, scratch1); - solve(scratch1, scratch1); - if (scratch1[col] == (T)0) { + Vector scratch(basis.First(), basis.Last()); + tab.GetColumn(matcol, scratch); + solve(scratch, scratch); + if (scratch[col] == static_cast(0)) { throw BadPivot(); } - E.push_back(EtaMatrix(col, scratch1)); + E.push_back({col, scratch}); total_operations += iterations * m + 2 * m * m; } } -template void LUdecomp::refactor() +template void LUDecomposition::refactor() { - - L = List>(); - U = List>(); - E = List>(); - P = List(); + L.clear(); + U.clear(); + E.clear(); if (!basis.IsIdent()) { FactorBasis(); @@ -152,14 +117,13 @@ template void LUdecomp::refactor() int m = basis.Last() - basis.First() + 1; total_operations = (m - 1) * m * (2 * m - 1) / 6; if (parent != nullptr) { - ((LUdecomp &)*parent).copycount--; + parent->copycount--; + parent = nullptr; } - parent = nullptr; } -template void LUdecomp::solveT(const Vector &c, Vector &y) const +template void LUDecomposition::solveT(const Vector &c, Vector &y) const { - if (c.First() != y.First() || c.Last() != y.Last()) { throw DimensionException(); } @@ -171,7 +135,7 @@ template void LUdecomp::solveT(const Vector &c, Vector &y) co if (!basis.IsIdent()) { BTransE(y); if (parent != nullptr) { - (*parent).solveT(y, y); + parent->solveT(y, y); } else { FTransU(y); @@ -180,9 +144,8 @@ template void LUdecomp::solveT(const Vector &c, Vector &y) co } } -template void LUdecomp::solve(const Vector &a, Vector &d) const +template void LUDecomposition::solve(const Vector &a, Vector &d) const { - if (a.First() != d.First() || a.Last() != d.Last()) { throw DimensionException(); } @@ -193,7 +156,7 @@ template void LUdecomp::solve(const Vector &a, Vector &d) con d = a; if (!basis.IsIdent()) { if (parent != nullptr) { - (*parent).solve(a, d); + parent->solve(a, d); } else { LPd_Trans(d); @@ -203,110 +166,88 @@ template void LUdecomp::solve(const Vector &a, Vector &d) con } } -template void LUdecomp::SetRefactor(int a) { refactor_number = a; } - // ----------------- // Private Members // ----------------- -template void LUdecomp::FactorBasis() +template void LUDecomposition::FactorBasis() { - int i, j, piv; - T pivVal; - Matrix B(basis.First(), basis.Last(), basis.First(), basis.Last()); - for (i = basis.First(); i <= basis.Last(); i++) { - tab.GetColumn(basis.Label(i), scratch2); + Vector scratch(basis.First(), basis.Last()); + for (int i = basis.First(); i <= basis.Last(); i++) { + tab.GetColumn(basis.Label(i), scratch); basis.CheckBasis(); - B.SetColumn(i, scratch2); + B.SetColumn(i, scratch); } - for (i = B.MinRow(); i <= B.MaxRow(); i++) { - pivVal = (T)Gambit::abs(B(i, i)); - piv = i; - for (j = i + 1; j <= B.MaxRow(); j++) { + for (int i = B.MinRow(); i <= B.MaxRow(); i++) { + T pivVal = Gambit::abs(B(i, i)); + int piv = i; + for (int j = i + 1; j <= B.MaxRow(); j++) { if (B(j, i) * B(j, i) > pivVal * pivVal) { piv = j; pivVal = B(j, i); } } - P.push_back(piv); B.SwitchRows(i, piv); - scratch2 = (T)0; - scratch2[i] = (T)1 / B(i, i); - for (j = i + 1; j <= B.MaxRow(); j++) { - scratch2[j] = -B(j, i) / B(i, i); + scratch = static_cast(0); + scratch[i] = static_cast(1) / B(i, i); + for (int j = i + 1; j <= B.MaxRow(); j++) { + scratch[j] = -B(j, i) / B(i, i); } - L.push_back(EtaMatrix(i, scratch2)); + L.push_back({piv, {i, scratch}}); GaussElem(B, i, i); } - for (j = B.MinCol(); j <= B.MaxCol(); j++) { - B.GetColumn(j, scratch2); - U.push_back(EtaMatrix(j, scratch2)); + for (int j = B.MinCol(); j <= B.MaxCol(); j++) { + B.GetColumn(j, scratch); + U.push_back({j, scratch}); } } -template void LUdecomp::GaussElem(Matrix &B, int row, int col) +template void LUDecomposition::GaussElem(Matrix &B, int row, int col) { - if (B(row, col) == (T)0) { + if (B(row, col) == static_cast(0)) { throw BadPivot(); } - int i, j; - - for (j = col + 1; j <= B.MaxCol(); j++) { + for (int j = col + 1; j <= B.MaxCol(); j++) { B(row, j) = B(row, j) / B(row, col); } - for (i = row + 1; i <= B.MaxRow(); i++) { - for (j = col + 1; j <= B.MaxCol(); j++) { + for (int i = row + 1; i <= B.MaxRow(); i++) { + for (int j = col + 1; j <= B.MaxCol(); j++) { B(i, j) = B(i, j) - (B(i, col) * B(row, j)); } } - for (i = row + 1; i <= B.MaxRow(); i++) { - B(i, col) = (T)0; + for (int i = row + 1; i <= B.MaxRow(); i++) { + B(i, col) = static_cast(0); } - B(row, col) = (T)1; + B(row, col) = static_cast(1); } -template void LUdecomp::BTransE(Vector &y) const +template void LUDecomposition::BTransE(Vector &y) const { - - int i; - for (i = E.Length(); i >= 1; i--) { - ((LUdecomp &)*this).scratch2 = y; - VectorEtaSolve(scratch2, E[i], y); - } + std::for_each(E.rbegin(), E.rend(), [&](const EtaMatrix &m) { VectorEtaSolve(m, y); }); } -template void LUdecomp::FTransU(Vector &y) const +template void LUDecomposition::FTransU(Vector &y) const { - - int i; - for (i = 1; i <= U.Length(); i++) { - ((LUdecomp &)*this).scratch2 = y; - VectorEtaSolve(scratch2, U[i], y); + for (const auto &eta : U) { + VectorEtaSolve(eta, y); } } template -void LUdecomp::VectorEtaSolve(const Vector &v, const EtaMatrix &eta, Vector &y) const +void LUDecomposition::VectorEtaSolve(const EtaMatrix &eta, Vector &y) const { - - int i, j; - - if (v.First() != y.First() || v.Last() != y.Last()) { - throw DimensionException(); - } - - for (i = v.First(); i <= v.Last(); i++) { - y[i] = v[i]; + Vector v = y; + for (int i = v.First(); i <= v.Last(); i++) { if (i == eta.col) { - for (j = v.First(); j <= v.Last(); j++) { + for (int j = v.First(); j <= v.Last(); j++) { if (j != eta.col) { y[i] -= v[j] * eta.etadata[j]; } @@ -316,42 +257,28 @@ void LUdecomp::VectorEtaSolve(const Vector &v, const EtaMatrix &eta, Ve } } -template void LUdecomp::FTransE(Vector &y) const +template void LUDecomposition::FTransE(Vector &y) const { - - int i; - for (i = 1; i <= E.Length(); i++) { - ((LUdecomp &)*this).scratch2 = y; - EtaVectorSolve(scratch2, E[i], y); + for (const auto &eta : E) { + EtaVectorSolve(eta, y); } } -template void LUdecomp::BTransU(Vector &y) const +template void LUDecomposition::BTransU(Vector &y) const { - - int i; - for (i = U.Length(); i >= 1; i--) { - ((LUdecomp &)*this).scratch2 = y; - EtaVectorSolve(scratch2, U[i], y); - } + std::for_each(U.rbegin(), U.rend(), [&](const EtaMatrix &m) { EtaVectorSolve(m, y); }); } template -void LUdecomp::EtaVectorSolve(const Vector &v, const EtaMatrix &eta, Vector &d) const +void LUDecomposition::EtaVectorSolve(const EtaMatrix &eta, Vector &d) const { - int i; - T temp; - - if (v.First() != d.First() || v.Last() != d.Last()) { - throw DimensionException(); - } - if (eta.etadata[eta.col] == (T)0) { + if (eta.etadata[eta.col] == static_cast(0)) { throw BadPivot(); // or we would have a singular matrix } + Vector v = d; + T temp = v[eta.col] / eta.etadata[eta.col]; - temp = v[eta.col] / eta.etadata[eta.col]; - - for (i = v.First(); i <= v.Last(); i++) { + for (int i = v.First(); i <= v.Last(); i++) { if (i == eta.col) { d[i] = temp; } @@ -361,105 +288,65 @@ void LUdecomp::EtaVectorSolve(const Vector &v, const EtaMatrix &eta, Ve } } -template void LUdecomp::yLP_Trans(Vector &y) const +template void LUDecomposition::yLP_Trans(Vector &y) const { - int j; - - for (j = L.Length(); j >= 1; j--) { - yLP_mult(y, j, ((LUdecomp &)*this).scratch2); - y = scratch2; + Vector scratch(basis.First(), basis.Last()); + for (int j = L.size() - 1; j >= 0; j--) { + yLP_mult(y, j, scratch); + y = scratch; } } -template void LUdecomp::yLP_mult(const Vector &y, int j, Vector &ans) const +template +void LUDecomposition::yLP_mult(const Vector &y, int j, Vector &ans) const { + int ell = j + y.First(); - if (ans.First() != y.First() || ans.Last() != y.Last()) { - throw DimensionException(); - } - T temp; - int i, k, l; - - l = j + y.First() - 1; - - for (i = y.First(); i <= y.Last(); i++) { - if (i != L[j].col) { + for (int i = y.First(); i <= y.Last(); i++) { + if (i != L[j].second.col) { ans[i] = y[i]; } else { - for (k = ans.First(), temp = (T)0; k <= ans.Last(); k++) { - temp += y[k] * L[j].etadata[k]; + T temp = static_cast(0); + for (int k = ans.First(); k <= ans.Last(); k++) { + temp += y[k] * L[j].second.etadata[k]; } ans[i] = temp; } } - - temp = ans[l]; - ans[l] = ans[P[j]]; - ans[P[j]] = temp; + std::swap(ans[ell], ans[L[j].first]); } -template void LUdecomp::LPd_Trans(Vector &d) const +template void LUDecomposition::LPd_Trans(Vector &d) const { - int j; - for (j = 1; j <= L.Length(); j++) { - LPd_mult(d, j, ((LUdecomp &)*this).scratch2); - d = scratch2; + Vector scratch(basis.First(), basis.Last()); + for (int j = 0; j < L.size(); j++) { + LPd_mult(d, j, scratch); + d = scratch; } } -template void LUdecomp::LPd_mult(Vector &d, int j, Vector &ans) const +template void LUDecomposition::LPd_mult(Vector &d, int j, Vector &ans) const { - - if (d.First() != ans.First() || d.Last() != ans.Last()) { - throw DimensionException(); - } - - T temp; - - int i, k; - - k = j + d.First() - 1; - temp = d[k]; - d[k] = d[P[j]]; - d[P[j]] = temp; - - for (i = d.First(); i <= d.Last(); i++) { - if (i == L[j].col) { - ans[i] = d[i] * L[j].etadata[i]; + int k = j + d.First(); + std::swap(d[k], d[L[j].first]); + for (int i = d.First(); i <= d.Last(); i++) { + if (i == L[j].second.col) { + ans[i] = d[i] * L[j].second.etadata[i]; } else { - ans[i] = d[i] + d[L[j].col] * L[j].etadata[i]; + ans[i] = d[i] + d[L[j].second.col] * L[j].second.etadata[i]; } } - - d[P[j]] = d[k]; - d[k] = temp; -} - -template bool LUdecomp::CheckBasis() -{ - int i; - bool ret = true; - - for (i = basis.First(); i <= basis.Last() && ret; i++) { - ret = ret && (basis.Label(i) == -i); - } - - return ret; + std::swap(d[L[j].first], d[k]); } -template bool LUdecomp::RefactorCheck() +template bool LUDecomposition::RefactorCheck() { int m = basis.Last() - basis.First() + 1; int i = iterations * (iterations * m + 2 * m * m); int k = total_operations + iterations * m + 2 * m * m; - bool tmp; - - tmp = (i > k); - return tmp; + return i > k; } -} // namespace linalg - -} // end namespace Gambit +} // end namespace Gambit::linalg diff --git a/src/solvers/linalg/tableau.h b/src/solvers/linalg/tableau.h index 2f633a5b4..f25abcc0a 100644 --- a/src/solvers/linalg/tableau.h +++ b/src/solvers/linalg/tableau.h @@ -69,7 +69,7 @@ template <> class Tableau : public TableauInterface { private: // The LU decomposition of the tableau - LUdecomp B; + LUDecomposition B; // A temporary column vector, to avoid frequent allocation mutable Vector tmpcol; }; From a65b12099a94ce3a73f1a1cbcb4ad5de31aedc3f Mon Sep 17 00:00:00 2001 From: Ted Turocy Date: Mon, 9 Dec 2024 11:42:43 +0000 Subject: [PATCH 35/99] Remove support for writing GTE XML files. This removes the option to write out extensive games in the Game Theory Explorer XML file format, as this format is no longer read or written by latest versions of GTE. --- ChangeLog | 4 ++ setup.py | 1 - src/pygambit/__init__.py | 1 - src/pygambit/game.pxi | 12 ++-- src/pygambit/gte.py | 130 --------------------------------------- tests/test_file.py | 4 -- 6 files changed, 9 insertions(+), 143 deletions(-) delete mode 100644 src/pygambit/gte.py diff --git a/ChangeLog b/ChangeLog index 08528e402..0bf614c0a 100644 --- a/ChangeLog +++ b/ChangeLog @@ -34,6 +34,10 @@ - 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.1.2] - unreleased diff --git a/setup.py b/setup.py index 301f56837..019940e71 100644 --- a/setup.py +++ b/setup.py @@ -133,7 +133,6 @@ def readme(): }, python_requires=">=3.9", install_requires=[ - "lxml", # used for reading/writing GTE files "numpy", "scipy", "deprecated", diff --git a/src/pygambit/__init__.py b/src/pygambit/__init__.py index d2abc1531..e959fd8da 100644 --- a/src/pygambit/__init__.py +++ b/src/pygambit/__init__.py @@ -23,7 +23,6 @@ from .gambit import * # noqa: F401,F403,I001 from . import ( # noqa: F401 - gte, # noqa: F401 nash, # noqa: F401 qre, # noqa: F401 supports, # noqa: F401 diff --git a/src/pygambit/game.pxi b/src/pygambit/game.pxi index 43bb57f60..df16d0b1f 100644 --- a/src/pygambit/game.pxi +++ b/src/pygambit/game.pxi @@ -25,7 +25,6 @@ import pathlib import numpy as np import scipy.stats -import pygambit.gte import pygambit.gameiter @@ -1037,8 +1036,6 @@ class Game: :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`. @@ -1059,11 +1056,12 @@ class Game: 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. + + .. versionchanged:: 16.3.0 + Removed support for writing Game Theory Explorer format as the XML format + is no longer supported by recent versions of GTE. """ - if format == "gte": - return pygambit.gte.write_game(self) - else: - return WriteGame(self.game, format.encode("ascii")).decode("ascii") + return WriteGame(self.game, format.encode("ascii")).decode("ascii") def _resolve_player(self, player: typing.Any, funcname: str, argname: str = "player") -> Player: diff --git a/src/pygambit/gte.py b/src/pygambit/gte.py deleted file mode 100644 index de72f7998..000000000 --- a/src/pygambit/gte.py +++ /dev/null @@ -1,130 +0,0 @@ -# -# This file is part of Gambit -# Copyright (c) 1994-2024, The Gambit Project (http://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/tests/test_file.py b/tests/test_file.py index 8a0d0e910..d891333f3 100644 --- a/tests/test_file.py +++ b/tests/test_file.py @@ -82,10 +82,6 @@ def test_parse_string_extra_payoff(self): "Parse error in game file: line 5:29: Expected '}'" ) - def test_write_game_gte_sanity(self): - g = pygambit.Game.parse_game(self.file_text) - g.write("gte") - class TestGambitNfgFile(unittest.TestCase): def setUp(self): From f6ff878830aa2c7799cc08d497946f20a4142a39 Mon Sep 17 00:00:00 2001 From: Ted Turocy Date: Tue, 3 Dec 2024 14:05:45 +0000 Subject: [PATCH 36/99] Modernisation of parts of VertexEnumerator and LPTableau --- src/solvers/enummixed/enummixed.cc | 12 +- src/solvers/linalg/lpsolve.imp | 97 +----- src/solvers/linalg/lptab.cc | 8 +- src/solvers/linalg/lptab.h | 41 +-- src/solvers/linalg/lptab.imp | 465 ++++------------------------- src/solvers/linalg/vertenum.h | 28 +- src/solvers/linalg/vertenum.imp | 120 ++------ 7 files changed, 131 insertions(+), 640 deletions(-) diff --git a/src/solvers/enummixed/enummixed.cc b/src/solvers/enummixed/enummixed.cc index 526eec779..439fbbf77 100644 --- a/src/solvers/enummixed/enummixed.cc +++ b/src/solvers/enummixed/enummixed.cc @@ -124,16 +124,16 @@ EnumMixedStrategySolver::SolveDetailed(const Game &p_game) const VertexEnumerator poly1(A1, b1); VertexEnumerator poly2(A2, b2); - const List> &verts1(poly1.VertexList()); - const List> &verts2(poly2.VertexList()); - solution->m_v1 = verts1.Length(); - solution->m_v2 = verts2.Length(); + const auto &verts1(poly1.VertexList()); + const auto &verts2(poly2.VertexList()); + solution->m_v1 = verts1.size(); + solution->m_v2 = verts2.size(); Array vert1id(solution->m_v1); Array vert2id(solution->m_v2); - for (int i = 1; i <= vert1id.Length(); vert1id[i++] = 0) + for (int i = 1; i <= vert1id.size(); vert1id[i++] = 0) ; - for (int i = 1; i <= vert2id.Length(); vert2id[i++] = 0) + for (int i = 1; i <= vert2id.size(); vert2id[i++] = 0) ; int i = 0; diff --git a/src/solvers/linalg/lpsolve.imp b/src/solvers/linalg/lpsolve.imp index 7d8a9ec3b..edb647932 100644 --- a/src/solvers/linalg/lpsolve.imp +++ b/src/solvers/linalg/lpsolve.imp @@ -44,8 +44,6 @@ LPSolve::LPSolve(const Matrix &A, const Vector &b, const Vector &c, well_formed = false; return; } - // gout << "\n--- Begin LPSolve ---\n"; - // tab.BigDump(gout); // initialize data int i, j, num_inequals, xlab, num_artific; @@ -54,8 +52,6 @@ LPSolve::LPSolve(const Matrix &A, const Vector &b, const Vector &c, num_artific = Artificials(b).Length(); nvars += num_artific; - // gout << "\n--- Begin Phase I ---\n"; - UB = new Array(nvars + neqns); LB = new Array(nvars + neqns); ub = new Array(nvars + neqns); @@ -96,17 +92,10 @@ LPSolve::LPSolve(const Matrix &A, const Vector &b, const Vector &c, } } - // gout << "\nUB = " << *UB << " " << "\nLB = " << *LB; - // gout << "\nub = " << *ub << " " << "\nlb = " << *lb; - // gout << "\ncost = " << (*cost); - // Initialize the tableau tab.SetCost(*cost); - // gout << "\nInitial Tableau = \n"; - // tab.Dump(gout); - // set xx to be initial feasible solution to phase II for (i = 1; i <= (*xx).Length(); i++) { if ((*LB)[i]) { @@ -127,36 +116,19 @@ LPSolve::LPSolve(const Matrix &A, const Vector &b, const Vector &c, } (*xx)[xlab] = x[i]; } - // gout << "\nxx: " << (*xx); Solve(1); total_cost = tab.TotalCost(); - // gout << "\nFinal Phase I tableau: "; - // tab.Dump(gout); - // gout << ", cost: " << total_cost; - - // gout << "\n--- End Phase I ---\n"; - - if (!bounded) { - // gout << "\nPhase 1 Unbounded\n"; - } - // assert(bounded); - // which eps should be used here? if (total_cost < -eps1) { feasible = false; - // gout << "\nProblem Infeasible\n\n"; return; } - // gout << "\n--- Begin Phase II ---\n"; - // Define Phase II upper and lower bounds for slack variables - // gout << "\nxx: " << (*xx); - for (i = num_inequals + 1; i <= neqns; i++) { (*UB)[nvars + i] = true; } @@ -175,42 +147,19 @@ LPSolve::LPSolve(const Matrix &A, const Vector &b, const Vector &c, (*cost)[i] = (T)0; } - // gout << "\nUB = " << *UB << " " << " LB = " << *LB; - // gout << "\nub = " << *ub << " " << " lb = " << *lb; - // gout << "\nc = " << (*cost); - tab.SetCost(*cost); - - // gout << "\nInitial basis: "; - // tab.Dump(gout); gout << '\n'; - Solve(2); - // gout << "\n--- End Phase II ---\n"; - - if (!bounded) { - // gout << "\nPhase II Unbounded\n"; - } total_cost = tab.TotalCost(); - tab.DualVector(y); + y = tab.GetDualVector(); opt_bfs = tab.GetBFS(); dual_bfs = tab.DualBFS(); - // gout << "\nFinal basis: "; - // tab.Dump(gout); gout << '\n'; - // gout << "\ncost: " << total_cost; - // gout << "DualVector = " << y << "\n"; - // gout << "\nopt_bfs:\n"; - // opt_bfs.Dump(gout); - // gout << "\n"; - // dual_bfs.Dump(gout); - for (i = 1; i <= neqns; i++) { if (dual_bfs.count(-i)) { opt_bfs.insert(-i, dual_bfs[-i]); } } - // gout << "\n--- End LPSolve ---\n"; } template Array LPSolve::Artificials(const Vector &b) @@ -233,14 +182,9 @@ template void LPSolve::Solve(int phase) do { // step 1: Solve y B = c_B - // tab.DualVector(y); // step 1: Solve y B = c_B - // gout << "\nstep 1, y: " << y; do { in = Enter(); // step 2: Choose entering variable - // gout << "\nstep 2, in: " << in; if (in) { - // tab.GetColumn(in,a); - // tab.Solve(a,d); // step 3: Solve B d = a tab.SolveColumn(in, d); // step 3: Solve B d = a, where a col #in of A out = Exit(in); // step 4: Choose leaving variable if (out == 0) { @@ -251,12 +195,10 @@ template void LPSolve::Solve(int phase) outlab = in; } else { - // gout << "\nstep 4, in: " << in << " out: " << out; outlab = tab.Label(out); } // update xx - for (i = 1; i <= x.Length(); i++) { // step 5a: - // gout << "\nstep 5a, i: " << i; + for (i = 1; i <= x.Length(); i++) { xlab = tab.Label(i); if (xlab < 0) { xlab = nvars - xlab; @@ -269,28 +211,20 @@ template void LPSolve::Solve(int phase) if (in < 0) { (*xx)[nvars - in] -= (T)flag * tmin; } - // gout << "\nstep 5a, xx: " << (*xx); } } while (outlab == in && outlab != 0); if (in) { - // gout << "\nstep 5b, Pivot in: " << in << " out: " << out; - tab.Pivot(out, in); // step5b: Pivot new variable into basis + tab.Pivot(out, in); tab.BasisVector(x); - // gout << "\n tab = "; - // tab.Dump(gout); - // gout << "\nxx: " << (*xx); - // gout << ", Cost = " << tab.TotalCost() << "\n"; if (phase == 1 && tab.TotalCost() >= -eps1) { return; } - // gout << "\nAfter pivot tab = \n"; } } while (in); } template int LPSolve::Enter() { - // gout << "\nIn LPSolve::Enter()"; int i, in; T rc; in = 0; @@ -303,26 +237,18 @@ template int LPSolve::Enter() } if (!tab.Member(lab)) { rc = tab.RelativeCost(lab); - // gout << "\nCost: " << tab.GetCost(); - // gout << "\n i = " << i << " cost: " << (*cost)[i] << " rc: " << rc << " test: " << test; if (rc > test + eps1) { if (!(*UB)[i] || ((*UB)[i] && (*xx)[i] - (*ub)[i] < -eps1)) { - { - test = rc; - in = lab; - flag = -1; - } - // gout << "\nflag: -1 in: " << in << " test: " << test; + test = rc; + in = lab; + flag = -1; } } if (-rc > test + eps1) { if (!(*LB)[i] || ((*LB)[i] && (*xx)[i] - (*lb)[i] > eps1)) { - { - test = -rc; - in = lab; - flag = 1; - } - // gout << "\nflag: +1 in: " << in << " test: " << test; + test = -rc; + in = lab; + flag = 1; } } } @@ -335,7 +261,6 @@ template int LPSolve::Exit(int in) int j, out, lab, col; T t; - // gout << "\nin Exit(), flag: " << flag; out = 0; tmin = (T)100000000; for (j = 1; j <= neqns; j++) { @@ -356,8 +281,6 @@ template int LPSolve::Exit(int in) tmin = t; out = j; } - // gout << "\nd[" << j << "]: " << d[j] << " col: " << col << " xx: " << (*xx)[col]; - // gout << " t: " << t << " tmin: " << tmin; } if (flag == 1) { t = (T)1000000000; @@ -371,8 +294,6 @@ template int LPSolve::Exit(int in) tmin = t; out = j; } - // gout << "\nd[" << j << "]: " << d[j] << " col: " << col << " xx: " << (*xx)[col]; - // gout << " t: " << t << " tmin: " << tmin; } } col = in; diff --git a/src/solvers/linalg/lptab.cc b/src/solvers/linalg/lptab.cc index 8635ffbf6..6c4eaee7e 100644 --- a/src/solvers/linalg/lptab.cc +++ b/src/solvers/linalg/lptab.cc @@ -22,13 +22,9 @@ #include "lptab.imp" -namespace Gambit { - -namespace linalg { +namespace Gambit::linalg { template class LPTableau; template class LPTableau; -} // namespace linalg - -} // end namespace Gambit +} // end namespace Gambit::linalg diff --git a/src/solvers/linalg/lptab.h b/src/solvers/linalg/lptab.h index 6a53cb757..c7f16e6db 100644 --- a/src/solvers/linalg/lptab.h +++ b/src/solvers/linalg/lptab.h @@ -25,53 +25,40 @@ #include "tableau.h" -namespace Gambit { - -namespace linalg { +namespace Gambit::linalg { template class LPTableau : public Tableau { private: Vector dual; - Array unitcost; - Array cost; - Array UB, LB; // does col have upper/lower bound? - Array ub, lb; // upper/lower bound + Vector unitcost; + Vector cost; void SolveDual(); public: - class BadPivot : public Exception { - public: - ~BadPivot() noexcept override = default; - const char *what() const noexcept override { return "Bad pivot in LPTableau."; } - }; LPTableau(const Matrix &A, const Vector &b); LPTableau(const Matrix &A, const Array &art, const Vector &b); - LPTableau(const LPTableau &); + LPTableau(const LPTableau &) = default; ~LPTableau() override = default; - LPTableau &operator=(const LPTableau &); + LPTableau &operator=(const LPTableau &) = default; // cost information void SetCost(const Vector &); // unit column cost := 0 - void SetCost(const Vector &, const Vector &); - Vector GetCost() const; - Vector GetUnitCost() const; - T TotalCost(); // cost of current solution + const Vector &GetCost() const { return cost; } + const Vector &GetUnitCost() const { return unitcost; } + T TotalCost() const; // cost of current solution T RelativeCost(int) const; // negative index convention - void RelativeCostVector(Vector &, Vector &); - void DualVector(Vector &) const; // column vector - // Redefined functions + const Vector &GetDualVector() const { return dual; } + void Refactor() override; void Pivot(int outrow, int col) override; - void ReversePivots(List> &); - bool IsReversePivot(int i, int j); - void DualReversePivots(List> &); + std::list> ReversePivots(); bool IsDualReversePivot(int i, int j); BFS DualBFS() const; // returns the label of the index of the last artificial variable - int LastLabel(); + int GetLastLabel() { return this->artificial.Last(); } // select Basis elements according to Tableau rows and cols void BasisSelect(const Array &rowv, Vector &colv) const; @@ -80,8 +67,6 @@ template class LPTableau : public Tableau { void BasisSelect(const Array &unitv, const Array &rowv, Vector &colv) const; }; -} // namespace linalg - -} // end namespace Gambit +} // end namespace Gambit::linalg #endif // LPTAB_H diff --git a/src/solvers/linalg/lptab.imp b/src/solvers/linalg/lptab.imp index 45e424ed2..231b8afeb 100644 --- a/src/solvers/linalg/lptab.imp +++ b/src/solvers/linalg/lptab.imp @@ -22,9 +22,7 @@ #include "lptab.h" -namespace Gambit { - -namespace linalg { +namespace Gambit::linalg { // --------------------------------------------------------------------------- // LPTableau member definitions @@ -44,69 +42,35 @@ LPTableau::LPTableau(const Matrix &A, const Array &art, const Vector< { } -template -LPTableau::LPTableau(const LPTableau &orig) - : Tableau(orig), dual(orig.dual), unitcost(orig.unitcost), cost(orig.cost) -{ -} - -template LPTableau &LPTableau::operator=(const LPTableau &orig) -{ - Tableau::operator=(orig); - if (this != &orig) { - dual = orig.dual; - unitcost = orig.unitcost; - cost = orig.cost; - } - return *this; -} - // cost-based functions template <> void LPTableau::SetCost(const Vector &c) { - int i; if (cost.First() == c.First() && cost.Last() == c.Last()) { - for (i = cost.First(); i <= cost.Last(); i++) { - cost[i] = c[i] * (Rational)Tableau::TotDenom(); - } - for (i = unitcost.First(); i <= unitcost.Last(); i++) { - unitcost[i] = (Rational)0; + cost = c; + cost *= Rational(Tableau::TotDenom()); + for (int i = unitcost.First(); i <= unitcost.Last(); i++) { + unitcost[i] = Rational(0); } Refactor(); SolveDual(); return; } - if (c.First() != cost.First()) { - throw DimensionException(); + for (int i = c.First(); i <= cost.Last(); i++) { + cost[i] = c[i] * Rational(Tableau::TotDenom()); } - if (c.Last() != (cost.Last() + unitcost.Length())) { - throw DimensionException(); + for (int i = unitcost.First(); i <= unitcost.Last(); i++) { + unitcost[i] = c[cost.size() + i - unitcost.First() + 1]; } - for (i = c.First(); i <= cost.Last(); i++) { - cost[i] = c[i] * (Rational)Tableau::TotDenom(); - } - for (i = unitcost.First(); i <= unitcost.Last(); i++) { - unitcost[i] = c[cost.Length() + i - unitcost.First() + 1]; - } - // gout << "\nc: " << c.First() << " " << c.Last() << " " << c; - // gout << "\ncost: " << cost.First() << " " << cost.Last() << " " << cost; - // gout << "\nunit: " << unitcost.First() << " " << unitcost.Last() << " " << unitcost; - //** added for Rational Refactor(); SolveDual(); } template <> void LPTableau::SetCost(const Vector &c) { - int i; if (cost.First() == c.First() && cost.Last() == c.Last()) { - for (i = cost.First(); i <= cost.Last(); i++) { - cost[i] = c[i]; - } - for (i = unitcost.First(); i <= unitcost.Last(); i++) { - unitcost[i] = (double)0; - } + cost = c; + unitcost = 0.0; Refactor(); SolveDual(); return; @@ -117,65 +81,24 @@ template <> void LPTableau::SetCost(const Vector &c) if (c.Last() != (cost.Last() + unitcost.Length())) { throw DimensionException(); } - for (i = c.First(); i <= cost.Last(); i++) { + for (int i = c.First(); i <= cost.Last(); i++) { cost[i] = c[i]; } - for (i = unitcost.First(); i <= unitcost.Last(); i++) { - unitcost[i] = c[cost.Length() + i - unitcost.First() + 1]; + for (int i = unitcost.First(); i <= unitcost.Last(); i++) { + unitcost[i] = c[cost.size() + i - unitcost.First() + 1]; } - // gout << "\nc: " << c.First() << " " << c.Last() << " " << c; - // gout << "\ncost: " << cost.First() << " " << cost.Last() << " " << cost; - // gout << "\nunit: " << unitcost.First() << " " << unitcost.Last() << " " << unitcost; - //** added for Rational Refactor(); SolveDual(); } -template void LPTableau::SetCost(const Vector &uc, const Vector &c) -{ - if (cost.First() != c.First() || cost.Last() != c.Last()) { - throw DimensionException(); - } - if (unitcost.First() != uc.First() || unitcost.Last() != uc.Last()) { - throw DimensionException(); - } - int i; - for (i = cost.First(); i <= cost.Last(); i++) { - cost[i] = c[i]; - } - for (i = unitcost.First(); i <= unitcost.Last(); i++) { - unitcost[i] = uc[i]; - } - SolveDual(); -} - -template Vector LPTableau::GetCost() const -{ - Vector x(cost.First(), cost.Last()); - for (int i = x.First(); i <= x.Last(); i++) { - x[i] = cost[i]; - } - return x; - // return cost; -} - -template Vector LPTableau::GetUnitCost() const -{ - Vector x(unitcost.First(), unitcost.Last()); - for (int i = x.First(); i <= x.Last(); i++) { - x[i] = unitcost[i]; - } - return x; -} - -template <> double LPTableau::TotalCost() +template <> double LPTableau::TotalCost() const { Vector tmpcol(MinRow(), MaxRow()); BasisSelect(unitcost, cost, tmpcol); return tmpcol * solution; } -template <> Rational LPTableau::TotalCost() +template <> Rational LPTableau::TotalCost() const { Vector tmpcol(MinRow(), MaxRow()); Vector sol(MinRow(), MaxRow()); @@ -190,15 +113,6 @@ template <> Rational LPTableau::TotalCost() return tmpcol * sol; } -template <> void LPTableau::DualVector(Vector &L) const { L = dual; } - -template <> void LPTableau::DualVector(Vector &out) const -{ - out = dual; - // for(int i=out.First();i<=out.Last();i++) - // if(Label(i)>=0) out[i]*=TotDenom(); -} - template T LPTableau::RelativeCost(int col) const { Vector tmpcol(this->MinRow(), this->MaxRow()); @@ -211,21 +125,6 @@ template T LPTableau::RelativeCost(int col) const } } -/* - -template -void LPTableau::RelativeCostVector(Vector &relunitcost, - Vector &relcost) const -{ - - if(!A->CheckColumn(relunitcost)) throw DimensionException(); - if(!A->CheckRow(relcost)) throw DimensionException(); - - relunitcost= unitcost - dual; - relcost= cost - dual*A; // pre multiplication not defined? -} -*/ - template void LPTableau::SolveDual() { Vector tmpcol1(this->MinRow(), this->MaxRow()); @@ -233,106 +132,83 @@ template void LPTableau::SolveDual() this->SolveT(tmpcol1, dual); } -// Redefined functions - template void LPTableau::Refactor() { - // gout << "\nIn LPTableau::Refactor()"; Tableau::Refactor(); SolveDual(); } template void LPTableau::Pivot(int outrow, int col) { - // gout << "\nIn LPTableau::Pivot() "; - // gout << "outrow: " << outrow << " col: " << col; - // BigDump(gout); Tableau::Pivot(outrow, col); SolveDual(); } -template void LPTableau::ReversePivots(List> &PivotList) +template std::list> LPTableau::ReversePivots() { + std::list> pivot_list; + Vector tmpcol(this->MinRow(), this->MaxRow()); - // gout << "\nIn LPTableau::ReversePivots"; bool flag; - int i, j, k, enter; + int k, enter; T ratio, a_ij, a_ik, b_i, b_k, c_j, c_k, c_jo, x; - List BestSet; - Array pivot(2); Vector tmpdual(this->MinRow(), this->MaxRow()); - Vector solution(tmpcol); //$$ - this->BasisVector(solution); //$$ - - // BigDump(gout); - // gout << "\ncost: " << GetCost(); - // gout << "\nunitcost: " << GetUnitCost() << "\n"; - // for(i=MinCol();i<=MaxCol();i++) gout << " " << RelativeCost(i); - // for(i=MinRow();i<=MaxRow();i++) gout << " " << RelativeCost(-i); + Vector solution(tmpcol); + this->BasisVector(solution); - for (j = -this->MaxRow(); j <= this->MaxCol(); j++) { + for (int j = -this->MaxRow(); j <= this->MaxCol(); j++) { if (j && !this->Member(j) && !this->IsBlocked(j)) { this->SolveColumn(j, tmpcol); - // gout << "\nColumn " << j; - // gout << "\nPivCol = " << tmpcol; - // gout << "\ncurrentSolCol = " << solution; - // find all i where prior tableau is primal feasible - - BestSet = List(); - for (i = this->MinRow(); i <= this->MaxRow(); i++) { + Array best_set; + for (int i = this->MinRow(); i <= this->MaxRow(); i++) { if (this->GtZero(tmpcol[i])) { - BestSet.push_back(i); + best_set.push_back(i); } } - if (BestSet.Length() > 0) { - ratio = solution[BestSet[1]] / tmpcol[BestSet[1]]; + if (!best_set.empty()) { + ratio = solution[best_set[1]] / tmpcol[best_set[1]]; // find max ratio - for (i = 2; i <= BestSet.Length(); i++) { - x = solution[BestSet[i]] / tmpcol[BestSet[i]]; + for (int i = 2; i <= best_set.size(); i++) { + x = solution[best_set[i]] / tmpcol[best_set[i]]; if (this->GtZero(x - ratio)) { ratio = x; } } // eliminate nonmaximizers - for (i = BestSet.Length(); i >= 1; i--) { - x = solution[BestSet[i]] / tmpcol[BestSet[i]]; + for (int i = best_set.size(); i >= 1; i--) { + x = solution[best_set[i]] / tmpcol[best_set[i]]; if (this->LtZero(x - ratio)) { - BestSet.Remove(i); + best_set.Remove(i); } } // check that j would be the row to exit in prior tableau // first check that prior pivot entry > 0 - for (i = BestSet.Length(); i >= 1; i--) { - a_ij = (T)1 / tmpcol[BestSet[i]]; + for (int i = best_set.size(); i >= 1; i--) { + a_ij = static_cast(1) / tmpcol[best_set[i]]; if (this->LeZero(a_ij)) { - // gout << "\nj not row to exit in prior tableau: a_ij <= 0"; - BestSet.Remove(i); + best_set.Remove(i); } else { // next check that prior pivot entry attains max ratio - b_i = solution[BestSet[i]] / tmpcol[BestSet[i]]; + b_i = solution[best_set[i]] / tmpcol[best_set[i]]; ratio = b_i / a_ij; flag = false; for (k = tmpcol.First(); k <= tmpcol.Last() && !flag; k++) { - if (k != BestSet[i]) { + if (k != best_set[i]) { a_ik = -a_ij * tmpcol[k]; b_k = solution[k] - b_i * tmpcol[k]; if (this->GtZero(a_ik) && this->GtZero(b_k / a_ik - ratio)) { - // gout << "\nj not row to exit in prior tableau: "; - // gout << "higher ratio at row= " << k; - BestSet.Remove(i); + best_set.Remove(i); flag = true; } else if (this->GtZero(a_ik) && this->EqZero(b_k / a_ik - ratio) && this->Label(k) < j) { - // gout << "\nj not row to exit in prior tableau: "; - // gout << "same ratio,lower lex at k= " << k; - BestSet.Remove(i); + best_set.Remove(i); flag = true; } } @@ -340,59 +216,32 @@ template void LPTableau::ReversePivots(List> &PivotList) } } } - // gout << "\nafter checking rows, BestSet = "; - // BestSet.Dump(gout); // check that i would be the column to enter in prior tableau - - for (i = BestSet.Length(); i >= 1; i--) { - enter = this->Label(BestSet[i]); - // gout << "\nenter = " << enter; - - tmpcol = (T)0; - tmpcol[BestSet[i]] = (T)1; - // gout << "\ntmpcol, loc 1: " << tmpcol; + for (int i = best_set.size(); i >= 1; i--) { + enter = this->Label(best_set[i]); + tmpcol = static_cast(0); + tmpcol[best_set[i]] = static_cast(1); this->SolveT(tmpcol, tmpdual); - // gout << "\ntmpcol, loc 2: " << tmpcol; - // gout << "\ntmpdual, loc 1: " << tmpdual; - - /* if( j<0 ) - { tmpcol=(T)0; tmpcol[-j]=(T)1; } - else - A->GetColumn(j,tmpcol); - */ this->GetColumn(j, tmpcol); - // gout << "\ncol " << j << ": " << tmpcol; a_ij = tmpdual * tmpcol; c_j = RelativeCost(j); if (this->EqZero(a_ij)) { - // gout << "\ni not col to enter in prior tableau: "; - // gout << "a_ij=0"; - BestSet.Remove(i); + best_set.Remove(i); } else { ratio = c_j / a_ij; - // gout << " ratio: " << ratio; if (enter < 0) { a_ik = tmpdual[-enter]; } else { this->GetColumn(enter, tmpcol); - // A->GetColumn(enter,tmpcol); a_ik = tmpdual * tmpcol; } c_k = RelativeCost(enter); c_jo = c_k - a_ik * ratio; - // gout << "\ntmpdual = " << tmpdual << "\n"; - // gout << " c_j:" << c_j; - // gout << " c_k:" << c_k; - // gout << " c_jo:" << c_jo; - // gout << " a_ij:" << a_ij; - // gout << " a_ik:" << a_ik; if (this->GeZero(c_jo)) { - // gout << "\ni not col to enter in prior tableau: "; - // gout << "c_jo<0"; - BestSet.Remove(i); + best_set.Remove(i); } else { flag = false; @@ -402,7 +251,6 @@ template void LPTableau::ReversePivots(List> &PivotList) a_ik = tmpdual[-k]; } else { - // A->GetColumn(k,tmpcol); this->GetColumn(k, tmpcol); a_ik = tmpdual * tmpcol; } @@ -410,9 +258,7 @@ template void LPTableau::ReversePivots(List> &PivotList) c_jo = c_k - a_ik * ratio; if (this->LtZero(c_jo)) { - // gout << "\ni not col to enter in prior tableau: "; - // gout << "c_jo < 0 for k = " << k; - BestSet.Remove(i); + best_set.Remove(i); flag = true; } } @@ -420,199 +266,52 @@ template void LPTableau::ReversePivots(List> &PivotList) } } } - // gout << "\nafter checking cols, BestSet = "; - // BestSet.Dump(gout); - - if (!BestSet.empty()) { - for (i = 1; i <= BestSet.Length(); i++) { - pivot[1] = BestSet[i]; - pivot[2] = j; - PivotList.push_back(pivot); - } - } - } - } -} - -template bool LPTableau::IsReversePivot(int i, int j) -{ - Vector tmpcol(this->MinRow(), this->MaxRow()); - - // first check that pivot preserves primal feasibility - - // gout << "\nin IsReversePivot, i= " << i << " j = "<< j; - this->SolveColumn(j, tmpcol); - Vector solution(tmpcol); //$$ - this->BasisVector(solution); //$$ - // gout << "\ncurrentPivCol = " << tmpcol; - // gout << "\ncurrentSolCol = " << solution; - if (this->LeZero(tmpcol[i])) { - // gout << "\nPrior tableau not primal feasible: currentPivCol[i] <= 0"; - return false; - } - int k; - T ratio = solution[i] / tmpcol[i]; - // gout << "\nratio = " << ratio; - - for (k = tmpcol.First(); k <= tmpcol.Last(); k++) { - if (this->GtZero(tmpcol[k]) && this->GtZero(solution[k] / tmpcol[k] - ratio)) { - // gout << "\nPrior tableau not primal feasible: i not min ratio"; - return false; - } - } - // check that j would be the row to exit in prior tableau - - T a_ij, a_ik, b_i, b_k, c_j, c_k, c_jo; - - a_ij = (T)1 / tmpcol[i]; - if (this->LeZero(a_ij)) { - // gout << "\nj not row to exit in prior tableau: a_ij <= 0"; - return false; - } - b_i = solution[i] / tmpcol[i]; - ratio = b_i / a_ij; - - for (k = tmpcol.First(); k <= tmpcol.Last(); k++) { - if (k != i) { - a_ik = -a_ij * tmpcol[k]; - b_k = solution[k] - b_i * tmpcol[k]; - if (this->GtZero(a_ik) && this->GtZero(b_k / a_ik - ratio)) { - // gout << "\nj not row to exit in prior tableau: "; - // gout << "higher ratio at row= " << k; - return false; - } - if (this->GtZero(a_ik) && this->EqZero(b_k / a_ik - ratio) && this->Label(k) < j) { - // gout << "\nj not row to exit in prior tableau: "; - // gout << "same ratio,lower lex at k= " << k; - return false; - } - } - } - - // check that i would be the column to enter in prior tableau - - int enter = this->Label(i); - // gout << "\nenter = " << enter; - - Vector tmpdual(this->MinRow(), this->MaxRow()); - tmpcol = (T)0; - tmpcol[i] = (T)1; - this->SolveT(tmpcol, tmpdual); - - /* - if( j<0 ) - { tmpcol=(T)0; tmpcol[-j]=(T)1; } - else - A->GetColumn(j,tmpcol); - */ - this->GetColumn(j, tmpcol); - - // gout << "\ncol j = " << tmpcol; - a_ij = tmpdual * tmpcol; - c_j = RelativeCost(j); - if (this->EqZero(a_ij)) { - // gout << "\ni not col to enter in prior tableau: "; - // gout << "a_ij=0"; - return false; - } - ratio = c_j / a_ij; - - if (enter < 0) { - a_ik = tmpdual[-enter]; - } - else { - // A->GetColumn(enter,tmpcol); - this->GetColumn(enter, tmpcol); - a_ik = tmpdual * tmpcol; - } - c_k = RelativeCost(enter); - c_jo = c_k - a_ik * ratio; - if (this->GeZero(c_jo)) { - // gout << "\ni not col to enter in prior tableau: "; - // gout << "c_jo<0"; - return false; - } - - for (k = -this->b->Last(); k < enter; k++) { - if (k != 0) { - if (k < 0) { - a_ik = tmpdual[-k]; - } - else { - // A->GetColumn(k,tmpcol); - this->GetColumn(k, tmpcol); - a_ik = tmpdual * tmpcol; - } - c_k = RelativeCost(k); - c_jo = c_k - a_ik * ratio; - if (this->LtZero(c_jo)) { - // gout << "\ni not col to enter in prior tableau: "; - // gout << "c_jo < 0 for k = " << k; - return false; + for (const auto &idx : best_set) { + Array pivot(2); + pivot[1] = idx; + pivot[2] = j; + pivot_list.push_back(pivot); } } } - // gout << "\nValid Reverse pivot at i = " << i << " j = " << j; - return true; + return pivot_list; } -template void LPTableau::DualReversePivots(List> & /*list*/) {} - template bool LPTableau::IsDualReversePivot(int i, int j) { // first check that pivot preserves dual feasibility - - // gout << "\nin IsDualReversePivot, i= " << i << " j = "<< j; - - int k; - Vector tmpcol(this->MinRow(), this->MaxRow()); Vector tmpdual(this->MinRow(), this->MaxRow()); - tmpcol = (T)0; - tmpcol[i] = (T)1; + tmpcol = static_cast(0); + tmpcol[i] = static_cast(1); this->SolveT(tmpcol, tmpdual); - Vector solution(tmpcol); //$$ - this->BasisVector(solution); //$$ - - // gout << "\ncurrentPivCol = " << tmpcol; - // gout << "\ncurrentSolCol = " << solution; + Vector solution(tmpcol); + this->BasisVector(solution); T a_ij, a_ik, c_j, c_k, ratio; - - /* if( j<0 ) - { tmpcol=(T)0; tmpcol[-j]=(T)1; } - else - A->GetColumn(j,tmpcol); - */ - this->GetColumn(j, tmpcol); a_ij = tmpdual * tmpcol; c_j = RelativeCost(j); if (this->GeZero(a_ij)) { - // gout << "\nPrior tableau not dual feasible: "; - // gout << "a_ij>=0"; return false; } ratio = c_j / a_ij; - for (k = -this->b->Last(); k <= cost.Last(); k++) { + for (int k = -this->b->Last(); k <= cost.Last(); k++) { if (k != 0) { if (k < 0) { a_ik = tmpdual[-k]; } else { - // A->GetColumn(k,tmpcol); this->GetColumn(k, tmpcol); a_ik = tmpdual * tmpcol; } c_k = RelativeCost(k); if (this->LtZero(a_ik) && this->GtZero(c_k / a_ik - ratio)) { - // gout << "\nPrior tableau not dual feasible: "; - // gout << "\nhigher ratio for k = " << k; return false; } } @@ -621,13 +320,10 @@ template bool LPTableau::IsDualReversePivot(int i, int j) // check that i would be the column to enter in prior tableau int enter = this->Label(i); - // gout << "\nenter = " << enter; - if (enter < 0) { a_ik = tmpdual[-enter]; } else { - // A->GetColumn(enter,tmpcol); this->GetColumn(enter, tmpcol); a_ik = tmpdual * tmpcol; } @@ -636,19 +332,16 @@ template bool LPTableau::IsDualReversePivot(int i, int j) c_k -= a_ik * c_j; if (this->GeZero(a_ik)) { - // gout << "\ni not col to enter in prior tableau: "; - // gout << "a_ik>=0"; return false; } ratio = c_k / a_ik; - for (k = -this->b->Last(); k <= cost.Last(); k++) { + for (int k = -this->b->Last(); k <= cost.Last(); k++) { if (k != 0) { if (k < 0) { a_ik = tmpdual[-k]; } else { - // A->GetColumn(k,tmpcol); this->GetColumn(k, tmpcol); a_ik = tmpdual * tmpcol; } @@ -657,81 +350,51 @@ template bool LPTableau::IsDualReversePivot(int i, int j) c_k -= a_ik * c_j; if (this->LtZero(a_ik) && this->GtZero(c_k / a_ik - ratio)) { - // gout << "\ni not col to enter in prior tableau: "; - // gout << "\nhigher ratio for k = " << k; return false; } if (k < enter && this->LtZero(a_ik) && this->EqZero(c_k / a_ik - ratio)) { - // gout << "\ni not col to enter in prior tableau: "; - // gout << "\nsame ratio and lower lex for k = " << k; return false; } } } // check that j would be the row to exit in prior tableau - this->SolveColumn(j, tmpcol); - // gout << "\ncurrentPivCol = " << tmpcol; - // gout << "\ncurrentSolCol = " << solution; T b_k, b_i; b_i = solution[i] / tmpcol[i]; if (this->LeZero(b_i)) { - // gout << "\nj not row to exit in prior tableau: "; - // gout << "b_i<=0"; return false; } - for (k = this->b->First(); k <= this->b->Last(); k++) { + for (int k = this->b->First(); k <= this->b->Last(); k++) { if (k != i) { b_k = solution[k] - b_i * tmpcol[k]; if (this->GtZero(b_k) && this->Label(k) < j) { - // gout << "\nj not row to exit in prior tableau: "; - // gout << "same ratio,lower lex at k= " << k; return false; } } } - // gout << "\nValid Reverse pivot at i = " << i << " j = " << j; return true; } -/* -template -BFS LPTableau::DualBFS() const -{ - BFS cbfs((T) 0); - for(int i=MinRow();i<=MaxRow();i++) { - if(!Member(-i)) - cbfs.Define(-i,dual[i]); - } - return cbfs; -} -*/ - template BFS LPTableau::DualBFS() const { BFS cbfs; - Vector d(this->MinRow(), this->MaxRow()); - DualVector(d); for (int i = this->MinRow(); i <= this->MaxRow(); i++) { if (!this->Member(-i)) { - cbfs.insert(-i, d[i]); + cbfs.insert(-i, dual[i]); } } - // gout << "\ndual: " << d; return cbfs; } -template int LPTableau::LastLabel() { return this->artificial.Last(); } - template void LPTableau::BasisSelect(const Array &rowv, Vector &colv) const { for (int i = this->basis.First(); i <= this->basis.Last(); i++) { if (this->basis.Label(i) < 0) { - colv[i] = (T)0; + colv[i] = static_cast(0); } else { colv[i] = rowv[this->basis.Label(i)]; @@ -752,6 +415,4 @@ void LPTableau::BasisSelect(const Array &unitv, const Array &rowv, Vect } } -} // namespace linalg - -} // end namespace Gambit +} // end namespace Gambit::linalg diff --git a/src/solvers/linalg/vertenum.h b/src/solvers/linalg/vertenum.h index 00d17ecd1..32c9a99c8 100644 --- a/src/solvers/linalg/vertenum.h +++ b/src/solvers/linalg/vertenum.h @@ -27,8 +27,7 @@ #include "lptab.h" #include "bfs.h" -namespace Gambit { -namespace linalg { +namespace Gambit::linalg { // // This class enumerates the vertices of the convex polyhedron @@ -44,35 +43,30 @@ namespace linalg { // template class VertexEnumerator { private: - int mult_opt, depth; + bool mult_opt; + int depth{0}; int n; // N is the number of columns, which is the # of dimensions. int k; // K is the number of inequalities given. const Matrix &A; const Vector &b; - Vector btemp, c; - Gambit::List> List; - Gambit::List> DualList; - Gambit::List> Verts; - long npivots, nodes; - Gambit::List visits, branches; + Vector btemp; + Array> m_list, m_duallist; + Array visits, branches; - void Enum(); void Deeper(); void Search(LPTableau &tab); void DualSearch(LPTableau &tab); public: VertexEnumerator(const Matrix &, const Vector &); - explicit VertexEnumerator(LPTableau &); + // explicit VertexEnumerator(LPTableau &); ~VertexEnumerator() = default; - const Gambit::List> &VertexList() const { return List; } - const Gambit::List> &DualVertexList() const { return DualList; } - void Vertices(Gambit::List> &verts) const; - long NumPivots() const { return npivots; } + const Array> &VertexList() const { return m_list; } + const Array> &DualVertexList() const { return m_duallist; } + std::list> GetVertices() const; }; -} // namespace linalg -} // end namespace Gambit +} // namespace Gambit::linalg #endif // GAMBIT_LINALG_VERTENUM_H diff --git a/src/solvers/linalg/vertenum.imp b/src/solvers/linalg/vertenum.imp index 654cd8674..1a1eec5aa 100644 --- a/src/solvers/linalg/vertenum.imp +++ b/src/solvers/linalg/vertenum.imp @@ -22,72 +22,17 @@ #include "vertenum.h" -namespace Gambit { -namespace linalg { +namespace Gambit::linalg { template VertexEnumerator::VertexEnumerator(const Matrix &A, const Vector &b) - : mult_opt(0), depth(0), A(A), b(b), btemp(b), c(A.MinCol(), A.MaxCol()), npivots(0), nodes(0) + : mult_opt(std::any_of(b.begin(), b.end(), [](const T &v) { return v == static_cast(0); })), + A(A), b(b), btemp(b) { - Enum(); -} - -template -VertexEnumerator::VertexEnumerator(LPTableau &tab) - : mult_opt(0), depth(0), A(tab.Get_A()), b(tab.Get_b()), btemp(tab.Get_b()), c(tab.GetCost()), - npivots(0), nodes(0) -{ - int i; - for (i = b.First(); i <= b.Last(); i++) { - if (b[i] == (T)0) { - mult_opt = 1; - } - } - - // Is this stuff right? - btemp = -(T)1; - - Vector uc(tab.MinRow(), tab.MaxRow()); - c = (T)1; - uc = (T)1; - - for (i = -tab.MaxRow(); i <= -tab.MinRow(); i++) { - if (tab.Member(i)) { - uc[-i] = (T)0; - } - } - - for (i = tab.MinCol(); i <= tab.MaxCol(); i++) { - if (tab.Member(i)) { - c[i] = (T)0; - } - } - - tab.SetCost(uc, c); - DualSearch(tab); -} - -template void VertexEnumerator::Enum() -{ - // Check dimensions - if (A.NumRows() != b.Length() || A.NumColumns() != c.Length()) { - throw DimensionException(); - } - // assert(A.NumRows() == b.Length() && A.NumColumns() == c.Length()); - - // Initialize the tableau - int i; - - for (i = b.First(); i <= b.Last(); i++) { - if (b[i] == (T)0) { - mult_opt = 1; - } - } - - btemp = -(T)1; - c = (T)1; - + btemp = static_cast(-1); LPTableau tab(A, b); + Vector c(A.MinCol(), A.MaxCol()); + c = static_cast(1); tab.SetCost(c); DualSearch(tab); @@ -96,59 +41,47 @@ template void VertexEnumerator::Enum() template void VertexEnumerator::Deeper() { depth++; - if (visits.Length() < depth) { + if (visits.size() < depth) { visits.push_back(0); branches.push_back(0); } visits[depth] += 1; - nodes++; } template void VertexEnumerator::Search(LPTableau &tab) { Deeper(); - Gambit::List> PivotList; if (tab.IsLexMin()) { - List.push_back(tab.GetBFS1()); - DualList.push_back(tab.DualBFS()); - } - if (PivotList.Length() != 0) { - throw DimensionException(); + m_list.push_back(tab.GetBFS1()); + m_duallist.push_back(tab.DualBFS()); } - // assert(PivotList.Length()==0); - tab.ReversePivots(PivotList); // get list of reverse pivots - if (PivotList.Length()) { - branches[depth] += PivotList.Length(); + auto pivot_list = tab.ReversePivots(); + if (!pivot_list.empty()) { + branches[depth] += pivot_list.size(); LPTableau tab2(tab); - for (auto pivot : PivotList) { - npivots++; + for (auto pivot : pivot_list) { tab2 = tab; tab2.Pivot(pivot[1], pivot[2]); Search(tab2); } } - else { - // Report(); // Report progress at terminal leafs - } depth--; } template void VertexEnumerator::DualSearch(LPTableau &tab) { - int i, j; Deeper(); branches[depth] += 1; if (mult_opt) { - tab.SetConst(btemp); // install artifical constraint vector + tab.SetConst(btemp); // install artificial constraint vector LPTableau tab2(tab); - for (i = b.First(); i <= b.Last(); i++) { - if (b[i] == (T)0) { - for (j = -b.Last(); j <= c.Last(); j++) { + for (int i = b.First(); i <= b.Last(); i++) { + if (b[i] == static_cast(0)) { + for (int j = -b.Last(); j <= A.MaxCol(); j++) { if (j && !tab.Member(j) && !tab.IsBlocked(j)) { if (tab.IsDualReversePivot(i, j)) { branches[depth] += 1; - npivots++; tab2 = tab; tab2.Pivot(i, j); DualSearch(tab2); @@ -163,19 +96,20 @@ template void VertexEnumerator::DualSearch(LPTableau &tab) depth--; } -template void VertexEnumerator::Vertices(Gambit::List> &verts) const +template std::list> VertexEnumerator::GetVertices() const { - for (int i = 1; i <= List.Length(); i++) { + std::list> verts; + for (int i = 1; i <= m_list.size(); i++) { Vector vert(A.NumColumns()); - vert = (T)0; - for (int j = 1; j <= vert.Length(); j++) { - if (List[i].IsDefined(j)) { - vert[j] = -List[i](j); + vert = static_cast(0); + for (int j = 1; j <= vert.size(); j++) { + if (m_list[i].IsDefined(j)) { + vert[j] = -m_list[i](j); } } - verts.Append(vert); + verts.push_back(vert); } + return verts; } -} // namespace linalg -} // end namespace Gambit +} // end namespace Gambit::linalg From b96c025a091fc1ec86469e2405aa5aa997dd432a Mon Sep 17 00:00:00 2001 From: Ted Turocy Date: Mon, 9 Dec 2024 12:46:30 +0000 Subject: [PATCH 37/99] Complete migration of tests from unittest to pytest style. --- tests/test_file.py | 191 +++----- tests/test_game_resolve.py | 171 +++++++ tests/test_game_resolve_functions.py | 109 ----- tests/test_games/e02.efg | 10 + tests/test_games/e02.nfg | 3 + tests/test_nash.py | 294 ++++++----- tests/test_node.py | 702 +++++++++++++-------------- 7 files changed, 758 insertions(+), 722 deletions(-) create mode 100644 tests/test_game_resolve.py delete mode 100644 tests/test_game_resolve_functions.py create mode 100644 tests/test_games/e02.efg create mode 100644 tests/test_games/e02.nfg diff --git a/tests/test_file.py b/tests/test_file.py index d891333f3..f15177768 100644 --- a/tests/test_file.py +++ b/tests/test_file.py @@ -1,115 +1,78 @@ -import unittest - import pytest -import pygambit - - -class TestGambitEfgFile(unittest.TestCase): - def setUp(self): - with open("contrib/games/e02.efg") as f: - self.file_text = f.read() - - def tearDown(self): - pass - - def test_parse_string_empty(self): - with self.assertRaises(ValueError) as e: - pygambit.Game.parse_game("") - self.assertEqual( - str(e.exception), - "Parse error in game file: Empty file or string provided" - ) - - def test_parse_string_no_newline_end(self): - pygambit.Game.parse_game( - 'NFG 1 R "prisoners dilemma"\n {"Player 1" "Player 2"} {2 2}\n' - ' -6 -6 -10 0 0 -10 -1 -1.0' - ) - - def test_parse_string_wrong_magic(self): - ft = self.file_text.replace("EFG", "") - with self.assertRaises(ValueError) as e: - pygambit.Game.parse_game(ft) - self.assertEqual( - str(e.exception), - "Parse error in game file: line 1:3: Expecting file type" - ) - - def test_parse_string_wrong_version(self): - ft = self.file_text.replace("EFG 2", "EFG 1") - with self.assertRaises(ValueError) as e: - pygambit.Game.parse_game(ft) - self.assertEqual( - str(e.exception), - "Parse error in game file: line 1:6: Accepting only EFG version 2" - ) - - def test_parse_string_wrong_precision(self): - ft = self.file_text.replace("EFG 2 R", "EFG 2 X") - with self.assertRaises(ValueError) as e: - pygambit.Game.parse_game(ft) - self.assertEqual( - str(e.exception), - "Parse error in game file: line 1:9: " - "Accepting only EFG R or D data type" - ) - - def test_parse_string_node_type(self): - ft = self.file_text.replace('p "" 1 1', 'x "" 1 1') - with self.assertRaises(ValueError) as e: - pygambit.Game.parse_game(ft) - self.assertEqual( - str(e.exception), - "Parse error in game file: line 4:3: Invalid type of node" - ) - - def test_parse_string_removed_player(self): - ft = self.file_text.replace('"Player 2"', "") - with self.assertRaises(ValueError) as e: - pygambit.Game.parse_game(ft) - self.assertEqual( - str(e.exception), - "Parse error in game file: line 5:26: Expected '}'" - ) - - def test_parse_string_extra_payoff(self): - ft = self.file_text.replace("1, 1", "1, 2, 3") - with self.assertRaises(ValueError) as e: - pygambit.Game.parse_game(ft) - self.assertEqual( - str(e.exception), - "Parse error in game file: line 5:29: Expected '}'" - ) - - -class TestGambitNfgFile(unittest.TestCase): - def setUp(self): - with open("contrib/games/e02.nfg") as f: - self.file_text = f.read() - - def tearDown(self): - pass - - def test_parse_string_removed_title(self): - ft = self.file_text.replace( - '"Selten (IJGT, 75), Figure 2, normal form"', "" - ) - with self.assertRaises(ValueError) as e: - pygambit.Game.parse_game(ft) - self.assertEqual( - str(e.exception), - "Parse error in game file: line 1:11: Game title missing" - ) - - def test_parse_string_removed_player(self): - ft = self.file_text.replace('"Player 2"', "") - with self.assertRaises(ValueError) as e: - pygambit.Game.parse_game(ft) - self.assertEqual( - str(e.exception), - "Parse error in game file: line 1:73: Expected '}'" - ) +import pygambit as gbt + + +def test_string_empty(): + with pytest.raises(ValueError) as excinfo: + gbt.Game.parse_game("") + assert "Parse error in game file: Empty file or string provided" in str(excinfo.value) + + +def test_efg_no_newline_end(): + gbt.Game.parse_game( + 'NFG 1 R "prisoners dilemma"\n {"Player 1" "Player 2"} {2 2}\n' + ' -6 -6 -10 0 0 -10 -1 -1.0' + ) + + +def test_string_wrong_magic(): + with open("tests/test_games/e01.efg") as f: + file_text = f.read().replace("EFG", "") + with pytest.raises(ValueError) as excinfo: + gbt.Game.parse_game(file_text) + assert "Parse error in game file: line 1:3: Expecting file type" in str(excinfo.value) + + +def test_efg_unsupported_version(): + with open("tests/test_games/e01.efg") as f: + file_text = f.read().replace("EFG 2", "EFG 1") + with pytest.raises(ValueError) as excinfo: + gbt.Game.parse_game(file_text) + assert "Parse error in game file: line 1:6: Accepting only EFG version 2" in str(excinfo.value) + + +def test_efg_unsupported_precision(): + with open("tests/test_games/e01.efg") as f: + file_text = f.read().replace("EFG 2 R", "EFG 2 X") + with pytest.raises(ValueError) as excinfo: + gbt.Game.parse_game(file_text) + assert ( + "Parse error in game file: line 1:9: Accepting only EFG R or D data type" + in str(excinfo.value) + ) + + +def test_efg_invalid_node_type(): + with open("tests/test_games/e02.efg") as f: + file_text = f.read().replace('p "" 1 1', 'x "" 1 1') + with pytest.raises(ValueError) as excinfo: + gbt.Game.parse_game(file_text) + assert "Parse error in game file: line 4:3: Invalid type of node" in str(excinfo) + + +def test_efg_payoffs_too_many(): + with open("tests/test_games/e02.efg") as f: + file_text = f.read().replace("1, 1", "1, 2, 3") + with pytest.raises(ValueError) as excinfo: + gbt.Game.parse_game(file_text) + assert "Parse error in game file: line 5:29: Expected '}'" in str(excinfo) + + +def test_nfg_title_missing(): + with open("tests/test_games/e02.nfg") as f: + file_text = f.read().replace('"Selten (IJGT, 75), Figure 2, normal form"', "") + with pytest.raises(ValueError) as excinfo: + gbt.Game.parse_game(file_text) + assert "Parse error in game file: line 1:11: Game title missing" in str(excinfo) + + +def test_nfg_player_missing(): + with open("tests/test_games/e02.nfg") as f: + file_text = f.read().replace('"Player 2"', "") + with pytest.raises(ValueError) as excinfo: + gbt.Game.parse_game(file_text) + assert "Parse error in game file: line 1:73: Expected '}'" in str(excinfo) def test_nfg_payoffs_not_enough(): @@ -118,7 +81,7 @@ def test_nfg_payoffs_not_enough(): 1 1 0 2 0 2 1 1 0 3 """ with pytest.raises(ValueError, match="Expected numerical payoff"): - pygambit.Game.parse_game(data) + gbt.Game.parse_game(data) def test_nfg_payoffs_too_many(): @@ -127,7 +90,7 @@ def test_nfg_payoffs_too_many(): 1 1 0 2 0 2 1 1 0 3 2 0 5 1 """ with pytest.raises(ValueError, match="end-of-file"): - pygambit.Game.parse_game(data) + gbt.Game.parse_game(data) def test_nfg_outcomes_not_enough(): @@ -148,7 +111,7 @@ def test_nfg_outcomes_not_enough(): 1 2 3 """ with pytest.raises(ValueError, match="Expected outcome index"): - pygambit.Game.parse_game(data) + gbt.Game.parse_game(data) def test_nfg_outcomes_too_many(): @@ -169,4 +132,4 @@ def test_nfg_outcomes_too_many(): 1 2 3 4 2 """ with pytest.raises(ValueError, match="end-of-file"): - pygambit.Game.parse_game(data) + gbt.Game.parse_game(data) diff --git a/tests/test_game_resolve.py b/tests/test_game_resolve.py new file mode 100644 index 000000000..e18c76794 --- /dev/null +++ b/tests/test_game_resolve.py @@ -0,0 +1,171 @@ +import itertools +import typing + +import pytest + +import pygambit as gbt + +from . import games + + +def _test_valid_resolutions(collection: list, resolver: typing.Callable) -> None: + """Generic function to exercise resolving objects as themselves or via existing labels.""" + for label, objects in itertools.groupby( + sorted(collection, key=lambda x: x.label), lambda x: x.label + ): + objects = list(objects) + # Objects resolve to themselves + for obj in objects: + assert obj == resolver(obj, "test") + # Ambiguous labels raise ValueError + if len(objects) > 1: + with pytest.raises(ValueError): + _ = resolver(label, "test") + else: + assert objects[0] == resolver(label, "test") + + +@pytest.mark.parametrize( + "game", + [ + games.read_from_file("sample_extensive_game.efg"), + ] +) +def test_resolve_player(game: gbt.Game) -> None: + _test_valid_resolutions(game.players, + lambda label, fn: game._resolve_player(label, fn)) + + +@pytest.mark.parametrize( + "game,player,exception", + [ + (games.read_from_file("sample_extensive_game.efg"), "", ValueError), + (games.read_from_file("sample_extensive_game.efg"), " ", ValueError), + (games.read_from_file("sample_extensive_game.efg"), "random", KeyError), + ] +) +def test_resolve_player_invalid(game: gbt.Game, player: str, exception: BaseException) -> None: + with pytest.raises(exception): + game._resolve_player(player, "test_resolve_player_invalid") + + +@pytest.mark.parametrize( + "game", + [ + games.read_from_file("sample_extensive_game.efg"), + ] +) +def test_resolve_outcome(game: gbt.Game) -> None: + _test_valid_resolutions(game.outcomes, + lambda label, fn: game._resolve_outcome(label, fn)) + + +@pytest.mark.parametrize( + "game,outcome,exception", + [ + (games.read_from_file("sample_extensive_game.efg"), "", ValueError), + (games.read_from_file("sample_extensive_game.efg"), " ", ValueError), + (games.read_from_file("sample_extensive_game.efg"), "nosuchoutcome", KeyError), + ] +) +def test_resolve_outcome_invalid(game: gbt.Game, outcome: str, exception: BaseException) -> None: + with pytest.raises(exception): + game._resolve_outcome(outcome, "test_resolve_outcome_invalid") + + +@pytest.mark.parametrize( + "game", + [ + games.read_from_file("sample_extensive_game.efg"), + ] +) +def test_resolve_strategy(game: gbt.Game) -> None: + _test_valid_resolutions(game.strategies, + lambda label, fn: game._resolve_strategy(label, fn)) + + +@pytest.mark.parametrize( + "game,strategy,exception", + [ + (games.read_from_file("sample_extensive_game.efg"), "", ValueError), + (games.read_from_file("sample_extensive_game.efg"), " ", ValueError), + (games.read_from_file("sample_extensive_game.efg"), "doesntexist", KeyError), + ] +) +def test_resolve_strategy_invalid( + game: gbt.Game, strategy: str, exception: BaseException +) -> None: + with pytest.raises(exception): + game._resolve_strategy(strategy, "test_resolve_strategy_invalid") + + +@pytest.mark.parametrize( + "game", + [ + games.read_from_file("sample_extensive_game.efg"), + ] +) +def test_resolve_node(game: gbt.Game) -> None: + _test_valid_resolutions(game.nodes(), + lambda label, fn: game._resolve_node(label, fn)) + + +@pytest.mark.parametrize( + "game,node,exception", + [ + (games.read_from_file("sample_extensive_game.efg"), "", ValueError), + (games.read_from_file("sample_extensive_game.efg"), " ", ValueError), + (games.read_from_file("sample_extensive_game.efg"), "fictitious", KeyError), + ] +) +def test_resolve_node_invalid(game: gbt.Game, node: str, exception: BaseException) -> None: + with pytest.raises(exception): + game._resolve_node(node, "test_resolve_node_invalid") + + +@pytest.mark.parametrize( + "game", + [ + games.read_from_file("sample_extensive_game.efg"), + ] +) +def test_resolve_infoset(game: gbt.Game) -> None: + _test_valid_resolutions(game.infosets, + lambda label, fn: game._resolve_infoset(label, fn)) + + +@pytest.mark.parametrize( + "game,infoset,exception", + [ + (games.read_from_file("sample_extensive_game.efg"), "", ValueError), + (games.read_from_file("sample_extensive_game.efg"), " ", ValueError), + (games.read_from_file("sample_extensive_game.efg"), "neverhappens", KeyError), + ] +) +def test_resolve_infoset_invalid(game: gbt.Game, infoset: str, exception: BaseException) -> None: + with pytest.raises(exception): + game._resolve_infoset(infoset, "test_resolve_infoset_invalid") + + +@pytest.mark.parametrize( + "game", + [ + games.read_from_file("sample_extensive_game.efg"), + ] +) +def test_resolve_action(game: gbt.Game) -> None: + _test_valid_resolutions(game.actions, + lambda label, fn: game._resolve_action(label, fn)) + + +@pytest.mark.parametrize( + "game,action,exception", + [ + (games.read_from_file("sample_extensive_game.efg"), "", ValueError), + (games.read_from_file("sample_extensive_game.efg"), " ", ValueError), + (games.read_from_file("sample_extensive_game.efg"), "inaction", KeyError), + ] +) +def test_resolve_action_invalid(game: gbt.Game, action: str, exception: BaseException) -> None: + with pytest.raises(exception): + game._resolve_action(action, "test_resolve_action_invalid") diff --git a/tests/test_game_resolve_functions.py b/tests/test_game_resolve_functions.py deleted file mode 100644 index 4fa8c2606..000000000 --- a/tests/test_game_resolve_functions.py +++ /dev/null @@ -1,109 +0,0 @@ -import unittest - -from . import games - - -class TestGambitResolveFunctions(unittest.TestCase): - def setUp(self): - self.game1 = games.read_from_file("sample_extensive_game.efg") - - # has named outcomes - self.game2 = games.read_from_file("basic_extensive_game.efg") - - # has named infosets - self.game3 = games.read_from_file("mixed_behavior_game.efg") - - def tearDown(self): - del self.game1 - del self.game2 - del self.game3 - - def test_resolve_player_empty_strings(self): - """Test _resolve_player with the empty string or strings of all spaces""" - self.assertRaises(ValueError, self.game1._resolve_player, player="", funcname="test") - self.assertRaises(ValueError, self.game1._resolve_player, player=" ", funcname="test") - self.assertRaises(ValueError, self.game2._resolve_player, player="", funcname="test") - self.assertRaises(ValueError, self.game2._resolve_player, player=" ", funcname="test") - - def test_resolve_player_nonempty_strings(self): - """Test _resolve_player with non-empty strings, some that resolve some that don't""" - assert self.game1._resolve_player(player="Player 1", funcname="test") - assert self.game1._resolve_player(player="Player 2", funcname="test") - self.assertRaises(KeyError, self.game1._resolve_player, player="Player 3", - funcname="test") - - def test_resolve_outcome_empty_strings(self): - """Test _resolve_outcome with empty string or strings of all spaces""" - self.assertRaises(ValueError, self.game1._resolve_outcome, outcome="", funcname="test") - self.assertRaises(ValueError, self.game1._resolve_outcome, outcome=" ", funcname="test") - self.assertRaises(ValueError, self.game2._resolve_outcome, outcome="", funcname="test") - self.assertRaises(ValueError, self.game2._resolve_outcome, outcome=" ", funcname="test") - - def test_resolve_outcome_nonempty_strings(self): - """Test _resolve_outcome with non-empty strings, some that resolve some that don't""" - assert self.game2._resolve_outcome(outcome="Outcome 1", funcname="test") - assert self.game2._resolve_outcome(outcome="Outcome 2", funcname="test") - self.assertRaises(KeyError, self.game2._resolve_outcome, outcome="Outcome 5", - funcname="test") - - def test_resolve_strategy_empty_strings(self): - """Test _resolve_strategy with empty string or strings of all spaces""" - self.assertRaises(ValueError, self.game1._resolve_strategy, strategy="", funcname="test") - self.assertRaises(ValueError, self.game1._resolve_strategy, strategy=" ", - funcname="test") - self.assertRaises(ValueError, self.game2._resolve_strategy, strategy="", funcname="test") - self.assertRaises(ValueError, self.game2._resolve_strategy, strategy=" ", - funcname="test") - - def test_resolve_strategy_nonempty_strings(self): - """Test _resolve_strategy with non-empty strings, some that resolve some that don't""" - assert self.game1._resolve_strategy(strategy="11", funcname="test") - assert self.game1._resolve_strategy(strategy="12", funcname="test") - self.assertRaises(KeyError, self.game1._resolve_strategy, strategy="13", funcname="test") - self.assertRaises(ValueError, self.game2._resolve_strategy, strategy="1", funcname="test") - self.assertRaises(ValueError, self.game2._resolve_strategy, strategy="2", funcname="test") - self.assertRaises(KeyError, self.game2._resolve_strategy, strategy="3", funcname="test") - - def test_resolve_node_empty_strings(self): - """Test _resolve_node with empty string or strings of all spaces""" - self.assertRaises(ValueError, self.game1._resolve_node, node="", funcname="test") - self.assertRaises(ValueError, self.game1._resolve_node, node=" ", funcname="test") - self.assertRaises(ValueError, self.game2._resolve_node, node="", funcname="test") - self.assertRaises(ValueError, self.game2._resolve_node, node=" ", funcname="test") - - def test_resolve_node_nonempty_strings(self): - """Test _resolve_node with non-empty strings, some that resolve some that don't""" - assert self.game1._resolve_node(node="1", funcname="test") - assert self.game1._resolve_node(node="2", funcname="test") - self.assertRaises(KeyError, self.game1._resolve_node, node="4", funcname="test") - self.assertRaises(KeyError, self.game2._resolve_node, node="1", funcname="test") - - def test_resolve_infoset_empty_strings(self): - """Test _resolve_infoset with empty string or strings of all spaces""" - self.assertRaises(ValueError, self.game1._resolve_infoset, infoset="", funcname="test") - self.assertRaises(ValueError, self.game1._resolve_infoset, infoset=" ", funcname="test") - self.assertRaises(ValueError, self.game2._resolve_infoset, infoset="", funcname="test") - self.assertRaises(ValueError, self.game2._resolve_infoset, infoset=" ", funcname="test") - - def test_resolve_infoset_nonempty_strings(self): - """Test _resolve_node with non-empty strings, some that resolve some that don't""" - assert self.game3._resolve_infoset(infoset="Infoset 1:1", funcname="test") - assert self.game3._resolve_infoset(infoset="Infoset 2:1", funcname="test") - self.assertRaises(KeyError, self.game3._resolve_infoset, infoset="Infoset 4:1", - funcname="test") - - def test_resolve_action_empty_strings(self): - """Test _resolve_action with empty string or strings of all spaces""" - self.assertRaises(ValueError, self.game1._resolve_action, action="", funcname="test") - self.assertRaises(ValueError, self.game1._resolve_action, action=" ", funcname="test") - self.assertRaises(ValueError, self.game2._resolve_action, action="", funcname="test") - self.assertRaises(ValueError, self.game2._resolve_action, action=" ", funcname="test") - - def test_resolve_action_nonempty_strings(self): - """Test _resolve_action with non-empty strings, some that resolve some that don't""" - self.assertRaises(ValueError, self.game1._resolve_action, action="1", funcname="test") - self.assertRaises(ValueError, self.game1._resolve_action, action="2", funcname="test") - self.assertRaises(KeyError, self.game1._resolve_action, action="3", funcname="test") - assert self.game2._resolve_action(action="U1", funcname="test") - assert self.game2._resolve_action(action="D1", funcname="test") - self.assertRaises(KeyError, self.game2._resolve_action, action="D4", funcname="test") diff --git a/tests/test_games/e02.efg b/tests/test_games/e02.efg new file mode 100644 index 000000000..e636e0276 --- /dev/null +++ b/tests/test_games/e02.efg @@ -0,0 +1,10 @@ +EFG 2 R "Selten (IJGT, 75), Figure 2" { "Player 1" "Player 2" } +"" + +p "" 1 1 "(1,1)" { "R" "L" } 0 +t "" 1 "Outcome 1" { 1, 1 } +p "" 2 1 "(2,1)" { "R" "L" } 0 +t "" 2 "Outcome 2" { 0, 2 } +p "" 1 2 "(1,2)" { "r" "l" } 0 +t "" 3 "Outcome 3" { 0, 3 } +t "" 4 "Outcome 4" { 2, 0 } diff --git a/tests/test_games/e02.nfg b/tests/test_games/e02.nfg new file mode 100644 index 000000000..e9723ea94 --- /dev/null +++ b/tests/test_games/e02.nfg @@ -0,0 +1,3 @@ +NFG 1 R "Selten (IJGT, 75), Figure 2, normal form" { "Player 1" "Player 2" } { 3 2 } + +1 1 0 2 0 2 1 1 0 3 2 0 diff --git a/tests/test_nash.py b/tests/test_nash.py index 06f8b4c7b..f47b69fb0 100644 --- a/tests/test_nash.py +++ b/tests/test_nash.py @@ -5,8 +5,6 @@ rigorous test suite for the algorithms across all games. """ -import unittest - import pytest import pygambit as gbt @@ -14,138 +12,166 @@ from . import games -class TestNash(unittest.TestCase): - """Test calls to Nash algorithms using Myerson poker - a game to which all algorithms apply.""" - def setUp(self): - self.poker = games.read_from_file("poker.efg") - self.mixed_rat = self.poker.mixed_strategy_profile( - rational=True, - data=[[gbt.Rational(1, 3), gbt.Rational(2, 3), gbt.Rational(0), gbt.Rational(0)], - [gbt.Rational(2, 3), gbt.Rational(1, 3)]] - ) - self.behav_rat = self.poker.mixed_behavior_profile(rational=True) - for action, prob in zip(self.poker.actions, - [1, 0, gbt.Rational(1, 3), gbt.Rational(2, 3), - gbt.Rational(2, 3), gbt.Rational(1, 3)]): - self.behav_rat[action] = prob - - def tearDown(self): - del self.poker - - def test_enumpure_strategy(self): - """Test calls of enumeration of pure strategies.""" - assert len(gbt.nash.enumpure_solve(self.poker, use_strategic=True).equilibria) == 0 - - def test_enumpure_agent(self): - """Test calls of enumeration of pure agent strategies.""" - assert len(gbt.nash.enumpure_solve(self.poker, use_strategic=False).equilibria) == 0 - - def test_enummixed_strategy_double(self): - """Test calls of enumeration of mixed strategy equilibria, floating-point.""" - result = gbt.nash.enummixed_solve(self.poker, rational=False) - assert len(result.equilibria) == 1 - # For floating-point results are not exact, so we skip testing exact values for now - - def test_enummixed_strategy_rational(self): - """Test calls of enumeration of mixed strategy equilibria, rational precision.""" - result = gbt.nash.enummixed_solve(self.poker, rational=True) - assert len(result.equilibria) == 1 - assert result.equilibria[0] == self.mixed_rat - - def test_lcp_strategy_double(self): - """Test calls of LCP for mixed strategy equilibria, floating-point.""" - result = gbt.nash.lcp_solve(self.poker, use_strategic=True, rational=False) - assert len(result.equilibria) == 1 - # For floating-point results are not exact, so we skip testing exact values for now - - def test_lcp_strategy_rational(self): - """Test calls of LCP for mixed strategy equilibria, rational precision.""" - result = gbt.nash.lcp_solve(self.poker, use_strategic=True, rational=True) - assert len(result.equilibria) == 1 - assert result.equilibria[0] == self.mixed_rat - - def test_lcp_behavior_double(self): - """Test calls of LCP for mixed behavior equilibria, floating-point.""" - result = gbt.nash.lcp_solve(self.poker, use_strategic=False, rational=False) - assert len(result.equilibria) == 1 - # For floating-point results are not exact, so we skip testing exact values for now - - def test_lcp_behavior_rational(self): - """Test calls of LCP for mixed behavior equilibria, rational precision.""" - result = gbt.nash.lcp_solve(self.poker, use_strategic=False, rational=True) - assert len(result.equilibria) == 1 - assert result.equilibria[0] == self.behav_rat - - def test_lp_strategy_double(self): - """Test calls of LP for mixed strategy equilibria, floating-point.""" - result = gbt.nash.lp_solve(self.poker, use_strategic=True, rational=False) - assert len(result.equilibria) == 1 - # For floating-point results are not exact, so we skip testing exact values for now - - def test_lp_strategy_rational(self): - """Test calls of LP for mixed strategy equilibria, rational precision.""" - result = gbt.nash.lp_solve(self.poker, use_strategic=True, rational=True) - assert len(result.equilibria) == 1 - assert result.equilibria[0] == self.mixed_rat - - def test_lp_behavior_double(self): - """Test calls of LP for mixed behavior equilibria, floating-point.""" - result = gbt.nash.lp_solve(self.poker, use_strategic=False, rational=False) - assert len(result.equilibria) == 1 - # For floating-point results are not exact, so we skip testing exact values for now - - def test_lp_behavior_rational(self): - """Test calls of LP for mixed behavior equilibria, rational precision.""" - result = gbt.nash.lp_solve(self.poker, use_strategic=False, rational=True) - assert len(result.equilibria) == 1 - assert result.equilibria[0] == self.behav_rat - - def test_liap_strategy(self): - """Test calls of liap for mixed strategy equilibria.""" - _ = gbt.nash.liap_solve(self.poker.mixed_strategy_profile()) - - def test_liap_behavior(self): - """Test calls of liap for mixed behavior equilibria.""" - _ = gbt.nash.liap_solve(self.poker.mixed_behavior_profile()) - - def test_simpdiv_strategy(self): - """Test calls of simplicial subdivision for mixed strategy equilibria.""" - result = gbt.nash.simpdiv_solve(self.poker.mixed_strategy_profile(rational=True)) - assert len(result.equilibria) == 1 - - def test_ipa_strategy(self): - """Test calls of IPA for mixed strategy equilibria.""" - result = gbt.nash.ipa_solve(self.poker) - assert len(result.equilibria) == 1 - - def test_gnm_strategy(self): - """Test calls of GNM for mixed strategy equilibria.""" - result = gbt.nash.gnm_solve(self.poker) - assert len(result.equilibria) == 1 - - def test_logit_strategy(self): - """Test calls of logit for mixed strategy equilibria.""" - result = gbt.nash.logit_solve(self.poker, use_strategic=True) - assert len(result.equilibria) == 1 - - def test_logit_behavior(self): - """Test calls of logit for mixed behavior equilibria.""" - result = gbt.nash.logit_solve(self.poker, use_strategic=False) - assert len(result.equilibria) == 1 - # gbt.nash.logit_behavior_atlambda(self.poker, 1.0) - - -# def test_logit_zerochance(): -# """Test handling zero-probability information sets when computing QRE.""" -# g = gbt.Game.new_tree(["Alice"]) -# g.append_move(g.root, g.players.chance, ["A", "B", "C"]) -# g.set_chance_probs(g.players.chance.infosets[0], [0, 0, 1]) -# g.append_move(g.root.children[0], "Alice", ["A", "B"]) -# g.append_infoset(g.root.children[1], g.root.children[0].infoset) -# win = g.add_outcome([1]) -# g.set_outcome(g.root.children[0].children[0], win) -# result = gbt.nash.logit_solve(g, use_strategic=False, maxregret=0.0001) -# assert result.equilibria[0].max_regret() < 0.0001 +def test_enumpure_strategy(): + """Test calls of enumeration of pure strategies.""" + game = games.read_from_file("poker.efg") + assert len(gbt.nash.enumpure_solve(game, use_strategic=True).equilibria) == 0 + + +def test_enumpure_agent(): + """Test calls of enumeration of pure agent strategies.""" + game = games.read_from_file("poker.efg") + assert len(gbt.nash.enumpure_solve(game, use_strategic=False).equilibria) == 0 + + +def test_enummixed_strategy_double(): + """Test calls of enumeration of mixed strategy equilibria, floating-point.""" + game = games.read_from_file("poker.efg") + result = gbt.nash.enummixed_solve(game, rational=False) + assert len(result.equilibria) == 1 + # For floating-point results are not exact, so we skip testing exact values for now + + +def test_enummixed_strategy_rational(): + """Test calls of enumeration of mixed strategy equilibria, rational precision.""" + game = games.read_from_file("poker.efg") + result = gbt.nash.enummixed_solve(game, rational=True) + assert len(result.equilibria) == 1 + expected = game.mixed_strategy_profile( + rational=True, + data=[[gbt.Rational(1, 3), gbt.Rational(2, 3), gbt.Rational(0), gbt.Rational(0)], + [gbt.Rational(2, 3), gbt.Rational(1, 3)]] + ) + assert result.equilibria[0] == expected + + +def test_lcp_strategy_double(): + """Test calls of LCP for mixed strategy equilibria, floating-point.""" + game = games.read_from_file("poker.efg") + result = gbt.nash.lcp_solve(game, use_strategic=True, rational=False) + assert len(result.equilibria) == 1 + # For floating-point results are not exact, so we skip testing exact values for now + + +def test_lcp_strategy_rational(): + """Test calls of LCP for mixed strategy equilibria, rational precision.""" + game = games.read_from_file("poker.efg") + result = gbt.nash.lcp_solve(game, use_strategic=True, rational=True) + assert len(result.equilibria) == 1 + expected = game.mixed_strategy_profile( + rational=True, + data=[[gbt.Rational(1, 3), gbt.Rational(2, 3), gbt.Rational(0), gbt.Rational(0)], + [gbt.Rational(2, 3), gbt.Rational(1, 3)]] + ) + assert result.equilibria[0] == expected + + +def test_lcp_behavior_double(): + """Test calls of LCP for mixed behavior equilibria, floating-point.""" + game = games.read_from_file("poker.efg") + result = gbt.nash.lcp_solve(game, use_strategic=False, rational=False) + assert len(result.equilibria) == 1 + # For floating-point results are not exact, so we skip testing exact values for now + + +def test_lcp_behavior_rational(): + """Test calls of LCP for mixed behavior equilibria, rational precision.""" + game = games.read_from_file("poker.efg") + result = gbt.nash.lcp_solve(game, use_strategic=False, rational=True) + assert len(result.equilibria) == 1 + expected = game.mixed_behavior_profile(rational=True, + data=[[[1, 0], + [gbt.Rational("1/3"), gbt.Rational("2/3")]], + [[gbt.Rational("2/3"), gbt.Rational("1/3")]]]) + assert result.equilibria[0] == expected + + +def test_lp_strategy_double(): + """Test calls of LP for mixed strategy equilibria, floating-point.""" + game = games.read_from_file("poker.efg") + result = gbt.nash.lp_solve(game, use_strategic=True, rational=False) + assert len(result.equilibria) == 1 + # For floating-point results are not exact, so we skip testing exact values for now + + +def test_lp_strategy_rational(): + """Test calls of LP for mixed strategy equilibria, rational precision.""" + game = games.read_from_file("poker.efg") + result = gbt.nash.lp_solve(game, use_strategic=True, rational=True) + assert len(result.equilibria) == 1 + expected = game.mixed_strategy_profile( + rational=True, + data=[[gbt.Rational(1, 3), gbt.Rational(2, 3), gbt.Rational(0), gbt.Rational(0)], + [gbt.Rational(2, 3), gbt.Rational(1, 3)]] + ) + assert result.equilibria[0] == expected + + +def test_lp_behavior_double(): + """Test calls of LP for mixed behavior equilibria, floating-point.""" + game = games.read_from_file("poker.efg") + result = gbt.nash.lp_solve(game, use_strategic=False, rational=False) + assert len(result.equilibria) == 1 + # For floating-point results are not exact, so we skip testing exact values for now + + +def test_lp_behavior_rational(): + """Test calls of LP for mixed behavior equilibria, rational precision.""" + game = games.read_from_file("poker.efg") + result = gbt.nash.lp_solve(game, use_strategic=False, rational=True) + assert len(result.equilibria) == 1 + expected = game.mixed_behavior_profile(rational=True, + data=[[[1, 0], + [gbt.Rational("1/3"), gbt.Rational("2/3")]], + [[gbt.Rational("2/3"), gbt.Rational("1/3")]]]) + assert result.equilibria[0] == expected + + +def test_liap_strategy(): + """Test calls of liap for mixed strategy equilibria.""" + game = games.read_from_file("poker.efg") + _ = gbt.nash.liap_solve(game.mixed_strategy_profile()) + + +def test_liap_behavior(): + """Test calls of liap for mixed behavior equilibria.""" + game = games.read_from_file("poker.efg") + _ = gbt.nash.liap_solve(game.mixed_behavior_profile()) + + +def test_simpdiv_strategy(): + """Test calls of simplicial subdivision for mixed strategy equilibria.""" + game = games.read_from_file("poker.efg") + result = gbt.nash.simpdiv_solve(game.mixed_strategy_profile(rational=True)) + assert len(result.equilibria) == 1 + + +def test_ipa_strategy(): + """Test calls of IPA for mixed strategy equilibria.""" + game = games.read_from_file("poker.efg") + result = gbt.nash.ipa_solve(game) + assert len(result.equilibria) == 1 + + +def test_gnm_strategy(): + """Test calls of GNM for mixed strategy equilibria.""" + game = games.read_from_file("poker.efg") + result = gbt.nash.gnm_solve(game) + assert len(result.equilibria) == 1 + + +def test_logit_strategy(): + """Test calls of logit for mixed strategy equilibria.""" + game = games.read_from_file("poker.efg") + result = gbt.nash.logit_solve(game, use_strategic=True) + assert len(result.equilibria) == 1 + + +def test_logit_behavior(): + """Test calls of logit for mixed behavior equilibria.""" + game = games.read_from_file("poker.efg") + result = gbt.nash.logit_solve(game, use_strategic=False) + assert len(result.equilibria) == 1 def test_logit_solve_branch_error_with_invalid_maxregret(): diff --git a/tests/test_node.py b/tests/test_node.py index 9b4c5eaf1..a35290756 100644 --- a/tests/test_node.py +++ b/tests/test_node.py @@ -1,386 +1,358 @@ -import unittest +import pytest -import pygambit +import pygambit as gbt from . import games -class TestGambitNode(unittest.TestCase): - def setUp(self): - self.game = pygambit.Game.new_tree() - self.extensive_game = ( - games.read_from_file("basic_extensive_game.efg") - ) - self.sample_extensive_game = ( - games.read_from_file("sample_extensive_game.efg") - ) - self.basic_game = ( - pygambit.Game.new_tree(players=["Player 1", "Player 2", "Player 3"]) - ) - self.basic_game.append_move(self.basic_game.root, "Player 1", ["U", "M", "D"]) - self.basic_game.append_move(self.basic_game.root.children[0], "Player 2", ["L", "R"]) - - def tearDown(self): - del self.game - del self.extensive_game - del self.sample_extensive_game - - def test_get_infoset(self): - "Test to ensure that we can retrieve an infoset for a given node" - assert self.extensive_game.root.infoset is not None - assert self.extensive_game.root.children[0].infoset is not None - assert ( - self.extensive_game.root.children[0].children[1].children[0] - .infoset is None - ) +def test_get_infoset(): + """Test to ensure that we can retrieve an infoset for a given node""" + game = games.read_from_file("basic_extensive_game.efg") + assert game.root.infoset is not None + assert game.root.children[0].infoset is not None + assert game.root.children[0].children[1].children[0].infoset is None - def test_get_outcome(self): - "Test to ensure that we can retrieve an outcome for a given node" - assert ( - self.extensive_game.root.children[0].children[1].children[0] - .outcome == self.extensive_game.outcomes[1] - ) - assert self.extensive_game.root.outcome is None - def test_get_player(self): - "Test to ensure that we can retrieve a player for a given node" - assert ( - self.extensive_game.root.player == - self.extensive_game.players[0] - ) - assert ( - self.extensive_game.root.children[0].children[1].children[0] - .player is None - ) +def test_get_outcome(): + """Test to ensure that we can retrieve an outcome for a given node""" + game = games.read_from_file("basic_extensive_game.efg") + assert game.root.children[0].children[1].children[0].outcome == game.outcomes[1] + assert game.root.outcome is None - def test_get_game(self): - "Test to ensure that we can retrieve the game object from a given node" - assert self.extensive_game == self.extensive_game.root.game - def test_get_parent(self): - "Test to ensure that we can retrieve a parent node for a given node" - assert ( - self.extensive_game.root.children[0].parent == - self.extensive_game.root - ) - assert self.extensive_game.root.parent is None +def test_get_player(): + """Test to ensure that we can retrieve a player for a given node""" + game = games.read_from_file("basic_extensive_game.efg") + assert game.root.player == game.players[0] + assert game.root.children[0].children[1].children[0].player is None - def test_get_prior_action(self): - "Test to ensure that we can retrieve the prior action for a given node" - assert ( - self.extensive_game.root.children[0].prior_action == - self.extensive_game.root.infoset.actions[0] - ) - assert self.extensive_game.root.prior_action is None - def test_get_prior_sibling(self): - "Test to ensure that we can retrieve a prior sibling of a given node" - assert ( - self.extensive_game.root.children[1].prior_sibling == - self.extensive_game.root.children[0] - ) - assert self.extensive_game.root.children[0].prior_sibling is None +def test_get_game(): + """Test to ensure that we can retrieve the game object from a given node""" + game = games.read_from_file("basic_extensive_game.efg") + assert game == game.root.game - def test_get_next_sibling(self): - "Test to ensure that we can retrieve a next sibling of a given node" - assert ( - self.extensive_game.root.children[0].next_sibling == - self.extensive_game.root.children[1] - ) - assert self.extensive_game.root.children[1].next_sibling is None - - def test_is_terminal(self): - "Test to ensure that we can check if a given node is a terminal node" - assert self.extensive_game.root.is_terminal is False - assert ( - self.extensive_game.root.children[0].children[0].children[0] - .is_terminal is True - ) - def test_is_successor_of(self): - """Test to ensure that we can check if a given node is a - successor of a supplied node - """ - assert ( - self.extensive_game.root.children[0].is_successor_of( - self.extensive_game.root - ) is True - ) - assert ( - self.extensive_game.root.is_successor_of( - self.extensive_game.root.children[0] - ) is False - ) - self.assertRaises( - TypeError, self.extensive_game.root.is_successor_of, 9 - ) - self.assertRaises( - TypeError, self.extensive_game.root.is_successor_of, "Test" - ) - self.assertRaises( - TypeError, - self.extensive_game.root.is_successor_of, - self.extensive_game.players[0] +def test_get_parent(): + """Test to ensure that we can retrieve a parent node for a given node""" + game = games.read_from_file("basic_extensive_game.efg") + assert game.root.children[0].parent == game.root + assert game.root.parent is None + + +def test_get_prior_action(): + """Test to ensure that we can retrieve the prior action for a given node""" + game = games.read_from_file("basic_extensive_game.efg") + assert game.root.children[0].prior_action == game.root.infoset.actions[0] + assert game.root.prior_action is None + + +def test_get_prior_sibling(): + """Test to ensure that we can retrieve a prior sibling of a given node""" + game = games.read_from_file("basic_extensive_game.efg") + assert game.root.children[1].prior_sibling == game.root.children[0] + assert game.root.children[0].prior_sibling is None + + +def test_get_next_sibling(): + """Test to ensure that we can retrieve a next sibling of a given node""" + game = games.read_from_file("basic_extensive_game.efg") + assert game.root.children[0].next_sibling == game.root.children[1] + assert game.root.children[1].next_sibling is None + + +def test_is_terminal(): + """Test to ensure that we can check if a given node is a terminal node""" + game = games.read_from_file("basic_extensive_game.efg") + assert game.root.is_terminal is False + assert game.root.children[0].children[0].children[0].is_terminal is True + + +def test_is_successor_of(): + """Test to ensure that we can check if a given node is a + successor of a supplied node + """ + game = games.read_from_file("basic_extensive_game.efg") + assert game.root.children[0].is_successor_of(game.root) + assert not game.root.is_successor_of(game.root.children[0]) + with pytest.raises(TypeError): + game.root.is_successor_of(9) + with pytest.raises(TypeError): + game.root.is_successor_of("Test") + with pytest.raises(TypeError): + game.root.is_successor_of(game.players[0]) + + +def test_is_subgame_root(): + """Test whether nodes are correctly labeled as roots of proper subgames.""" + game = games.read_from_file("basic_extensive_game.efg") + assert game.root.is_subgame_root + assert not game.root.children[0].is_subgame_root + + +def test_append_move_error_player_actions(): + """Test to ensure there are actions when appending with a player""" + game = games.read_from_file("basic_extensive_game.efg") + with pytest.raises(gbt.UndefinedOperationError): + game.append_move(game.root, game.players[0], []) + + +def test_append_move_error_player_mismatch(): + """Test to ensure the node and the player are from the same game""" + game1 = gbt.Game.new_tree() + game2 = games.read_from_file("basic_extensive_game.efg") + with pytest.raises(gbt.MismatchError): + game1.append_move(game1.root, game2.players[0], ["a"]) + + +def test_append_move_error_infoset_mismatch(): + """Test to ensure the node and the player are from the same game""" + game1 = gbt.Game.new_tree() + game2 = games.read_from_file("basic_extensive_game.efg") + with pytest.raises(gbt.MismatchError): + game1.append_infoset(game1.root, game2.players[0].infosets[0]) + + +def test_insert_move_error_player_actions(): + """Test to ensure there are actions when inserting with a player""" + game = games.read_from_file("basic_extensive_game.efg") + with pytest.raises(gbt.UndefinedOperationError): + game.insert_move(game.root, game.players[0], 0) + + +def test_insert_move_error_player_mismatch(): + """Test to ensure the node and the player are from the same game""" + game1 = gbt.Game.new_tree() + game2 = games.read_from_file("basic_extensive_game.efg") + with pytest.raises(gbt.MismatchError): + game1.insert_move(game1.root, game2.players[0], 1) + + +def test_node_leave_infoset(): + """Test to ensure it's possible to remove a node from an infoset""" + game = games.read_from_file("basic_extensive_game.efg") + assert len(game.infosets[1].members) == 2 + game.leave_infoset(game.root.children[0]) + assert len(game.infosets[1].members) == 1 + + +def test_node_delete_parent(): + """Test to ensure deleting a parent node works""" + game = games.read_from_file("basic_extensive_game.efg") + node = game.root.children[0] + game.delete_parent(node) + assert game.root == node + + +def test_node_delete_tree(): + """Test to ensure deleting every child of a node works""" + game = games.read_from_file("basic_extensive_game.efg") + node = game.root.children[0] + game.delete_tree(node) + assert len(node.children) == 0 + + +def test_node_copy_nonterminal(): + """Test on copying to a nonterminal node.""" + game = games.read_from_file("basic_extensive_game.efg") + with pytest.raises(gbt.UndefinedOperationError): + game.copy_tree(game.root, game.root) + + +def test_node_copy_across_games(): + """Test to ensure a gbt.MismatchError is raised when trying to copy a tree + from a different game. + """ + game1 = gbt.Game.new_tree() + game2 = games.read_from_file("basic_extensive_game.efg") + with pytest.raises(gbt.MismatchError): + game1.copy_tree(game1.root, game2.root) + with pytest.raises(gbt.MismatchError): + game1.copy_tree(game2.root, game1.root) + + +def test_node_move_nonterminal(): + """Test on moving to a nonterminal node.""" + game = games.read_from_file("basic_extensive_game.efg") + with pytest.raises(gbt.UndefinedOperationError): + game.move_tree(game.root, game.root) + + +def test_node_move_successor(): + """Test on moving a node to one of its successors.""" + game = games.read_from_file("basic_extensive_game.efg") + with pytest.raises(gbt.UndefinedOperationError): + game.move_tree(game.root, game.root.children[0].children[0].children[0]) + + +def test_node_move_across_games(): + """Test to ensure a gbt.MismatchError is raised when trying to move a tree + between different games. + """ + game1 = gbt.Game.new_tree() + game2 = games.read_from_file("basic_extensive_game.efg") + with pytest.raises(gbt.MismatchError): + game1.move_tree(game1.root, game2.root) + with pytest.raises(gbt.MismatchError): + game1.move_tree(game2.root, game1.root) + + +def test_append_move_creates_single_infoset_list_of_nodes(): + """Test that appending a list of nodes creates a single infoset.""" + game = games.read_from_file("sample_extensive_game.efg") + game.add_player("Player 3") + nodes = [game.root.children[1].children[0], + game.root.children[0].children[0], + game.root.children[0].children[1]] + game.append_move(nodes, "Player 3", ["B", "F"]) + assert len(game.players["Player 3"].infosets) == 1 + + +def test_append_move_same_infoset_list_of_nodes(): + """Test that nodes from a list of nodes are resolved in the same infoset.""" + game = games.read_from_file("sample_extensive_game.efg") + game.add_player("Player 3") + nodes = [game.root.children[1].children[0], game.root.children[0].children[0]] + game.append_move(nodes, "Player 3", ["B", "F"]) + assert nodes[0].infoset == nodes[1].infoset + + +def test_append_move_actions_list_of_nodes(): + """Test that nodes from a list of nodes that resolved in the same infoset + have the same actions. + """ + game = games.read_from_file("sample_extensive_game.efg") + game.add_player("Player 3") + nodes = [game.root.children[1].children[0], game.root.children[0].children[0]] + game.append_move(nodes, "Player 3", ["B", "F", "S"]) + action_list = list(game.players["Player 3"].infosets[0].actions) + for node in nodes: + assert list(node.infoset.actions) == action_list + + +def test_append_move_actions_list_of_node_labels(): + """Test that nodes from a list of node labels are resolved correctly.""" + game = games.read_from_file("sample_extensive_game.efg") + game.add_player("Player 3") + game.root.children[1].children[0].label = "0" + game.root.children[0].children[0].label = "00" + game.append_move(["0", "00"], "Player 3", ["B", "F", "S"]) + + assert game.root.children[1].children[0].children[0].parent.label == "0" + assert game.root.children[0].children[0].children[0].parent.label == "00" + assert len(game.root.children[1].children[0].children) == 3 + assert len(game.root.children[0].children[0].children) == 3 + + +def test_append_move_actions_list_of_mixed_node_references(): + """Test that nodes from a list of nodes with either 'node' or str references + are resolved correctly. + """ + game = games.read_from_file("sample_extensive_game.efg") + game.add_player("Player 3") + + game.root.children[1].children[0].label = " 000" + node_references = [" 000", game.root.children[0].children[0]] + game.append_move(node_references, "Player 3", ["B", "F", "S"]) + + assert game.root.children[1].children[0].children[0].parent.label == " 000" + assert len(game.root.children[1].children[0].children) == 3 + assert len(game.root.children[0].children[0].children) == 3 + + +def test_append_move_labels_list_of_nodes(): + """Test that nodes from a list of nodes that resolved in the same infoset + have the same labels per action. + """ + game = games.read_from_file("sample_extensive_game.efg") + game.add_player("Player 3") + nodes = [game.root.children[1].children[0], game.root.children[0].children[0]] + game.append_move(nodes, "Player 3", ["B", "F", "S"]) + + action_list = game.players["Player 3"].infosets[0].actions + tmp1 = game.root.children[1].children[0].infoset.actions + tmp2 = game.root.children[0].children[0].infoset.actions + + for (action, action1, action2) in zip(action_list, tmp1, tmp2): + assert action.label == action1.label + assert action.label == action2.label + + +def test_append_move_node_list_with_non_terminal_node(): + """Test that we get an UndefinedOperationError when we import in append_move a list + of nodes that has a non-terminal node. + """ + game = games.read_from_file("sample_extensive_game.efg") + game.add_player("Player 3") + with pytest.raises(gbt.UndefinedOperationError): + game.append_move( + [game.root.children[1], game.root.children[0].children[1]], + "Player 3", + ["B", "F"] ) - def test_is_subgame_root(self): - """Test whether nodes are correctly labeled as roots of proper subgames.""" - assert self.extensive_game.root.is_subgame_root is True - assert self.extensive_game.root.children[0].is_subgame_root is False - - def test_append_move_error_player_actions(self): - "Test to ensure there are actions when appending with a player" - self.assertRaises( - pygambit.UndefinedOperationError, - self.extensive_game.append_move, - self.extensive_game.root, - self.extensive_game.players[0], - [] + +def test_append_move_node_list_with_duplicate_node_references(): + """Test that we get a ValueError when we import in append_move a list + nodes with non-unique node references. + """ + game = games.read_from_file("sample_extensive_game.efg") + game.add_player("Player 3") + game.root.children[0].children[1].label = "00" + with pytest.raises(ValueError): + game.append_move( + ["00", game.root.children[1].children[0], game.root.children[0].children[1]], + "Player 3", + ["B", "F"] ) - def test_append_move_error_player_mismatch(self): - "Test to ensure the node and the player are from the same game" - self.assertRaises( - pygambit.pygambit.MismatchError, - self.game.append_move, - self.game.root, - self.extensive_game.players[0], - ["a"] + +def test_append_move_node_list_is_empty(): + """Test that we get a ValueError when we import in append_move an + empty list of nodes. + """ + game = games.read_from_file("sample_extensive_game.efg") + game.add_player("Player 3") + with pytest.raises(ValueError): + game.append_move([], "Player 3", ["B", "F"]) + + +def test_append_infoset_node_list_with_non_terminal_node(): + """Test that we get an UndefinedOperationError when we import in append_infoset + a list of nodes that has a non-terminal node. + """ + game = games.read_from_file("sample_extensive_game.efg") + game.add_player("Player 3") + game.append_move(game.root.children[0].children[0], "Player 3", ["B", "F"]) + with pytest.raises(gbt.UndefinedOperationError): + game.append_infoset( + [game.root.children[1], game.root.children[0].children[1]], + game.root.children[0].children[0].infoset ) - def test_append_move_error_infoset_mismatch(self): - "Test to ensure the node and the player are from the same game" - self.assertRaises(pygambit.MismatchError, - self.game.append_infoset, - self.game.root, - self.extensive_game.players[0].infosets[0]) - - def test_insert_move_error_player_actions(self): - "Test to ensure there are actions when inserting with a player" - self.assertRaises( - pygambit.UndefinedOperationError, - self.extensive_game.insert_move, - self.extensive_game.root, - self.extensive_game.players[0], - 0 + +def test_append_infoset_node_list_with_duplicate_node(): + """Test that we get a ValueError when we import in append_infoset a list + with non-unique elements. + """ + game = games.read_from_file("sample_extensive_game.efg") + game.add_player("Player 3") + game.append_move(game.root.children[0].children[0], "Player 3", ["B", "F"]) + with pytest.raises(ValueError): + game.append_infoset( + [game.root.children[0].children[1], + game.root.children[1].children[0], + game.root.children[0].children[1]], + game.root.children[0].children[0].infoset ) - def test_insert_move_error_player_mismatch(self): - "Test to ensure the node and the player are from the same game" - self.assertRaises(pygambit.MismatchError, - self.game.insert_move, - self.game.root, - self.extensive_game.players[0], - 1) - - def test_node_leave_infoset(self): - "Test to ensure it's possible to remove a node from an infoset" - assert len(self.extensive_game.infosets[1].members) == 2 - self.extensive_game.leave_infoset(self.extensive_game.root.children[0]) - assert len(self.extensive_game.infosets[1].members) == 1 - - def test_node_delete_parent(self): - "Test to ensure deleting a parent node works" - node = self.extensive_game.root.children[0] - node.game.delete_parent(node) - assert self.extensive_game.root == node - - def test_node_delete_tree(self): - "Test to ensure deleting every children of a node works" - node = self.extensive_game.root.children[0] - node.game.delete_tree(node) - assert len(node.children) == 0 - - def test_node_copy_nonterminal(self): - """Test on copying to a nonterminal node.""" - self.assertRaises(pygambit.UndefinedOperationError, self.extensive_game.copy_tree, - self.extensive_game.root, self.extensive_game.root) - - def test_node_copy_across_games(self): - """Test to ensure a pygambit.MismatchError is raised when trying to copy a tree - from a different game. - """ - self.assertRaises(pygambit.MismatchError, self.game.copy_tree, - self.game.root, self.extensive_game.root) - self.assertRaises(pygambit.MismatchError, self.game.copy_tree, - self.extensive_game.root, self.game.root) - - def test_node_move_nonterminal(self): - """Test on moving to a nonterminal node.""" - self.assertRaises(pygambit.UndefinedOperationError, self.extensive_game.move_tree, - self.extensive_game.root, self.extensive_game.root) - - def test_node_move_successor(self): - """Test on moving a node to one of its successors.""" - self.assertRaises(pygambit.UndefinedOperationError, self.extensive_game.move_tree, - self.extensive_game.root, - self.extensive_game.root.children[0].children[0].children[0]) - - def test_node_move_across_games(self): - """Test to ensure a pygambit.MismatchError is raised when trying to move a tree - between different games. - """ - self.assertRaises(pygambit.MismatchError, self.game.move_tree, - self.game.root, self.extensive_game.root) - self.assertRaises(pygambit.MismatchError, self.game.move_tree, - self.extensive_game.root, self.game.root) - - def test_append_move_creates_single_infoset_list_of_nodes(self): - """Test that appending a list of nodes creates a single infoset.""" - self.sample_extensive_game.add_player("Player 3") - nodes = [self.sample_extensive_game.root.children[1].children[0], - self.sample_extensive_game.root.children[0].children[0], - self.sample_extensive_game.root.children[0].children[1]] - self.sample_extensive_game.append_move(nodes, "Player 3", ["B", "F"]) - - assert len(self.sample_extensive_game.players["Player 3"].infosets) == 1 - - def test_append_move_same_infoset_list_of_nodes(self): - """Test that nodes from a list of nodes are resolved in the same infoset.""" - game = self.sample_extensive_game - game.add_player("Player 3") - nodes = [game.root.children[1].children[0], - game.root.children[0].children[0]] - game.append_move(nodes, "Player 3", ["B", "F"]) - - assert nodes[0].infoset == nodes[1].infoset - - def test_append_move_actions_list_of_nodes(self): - """Test that nodes from a list of nodes that resolved in the same infoset - have the same actions. - """ - game = self.sample_extensive_game - game.add_player("Player 3") - nodes = [game.root.children[1].children[0], - game.root.children[0].children[0]] - game.append_move(nodes, "Player 3", ["B", "F", "S"]) - - action_list = list(game.players["Player 3"].infosets[0].actions) - - for node in nodes: - assert list(node.infoset.actions) == action_list - - def test_append_move_actions_list_of_node_labels(self): - """Test that nodes from a list of node labels are resolved correctly. - """ - game = self.sample_extensive_game - game.add_player("Player 3") - game.root.children[1].children[0].label = "0" - game.root.children[0].children[0].label = "00" - nodes = ["0", "00"] - game.append_move(nodes, "Player 3", ["B", "F", "S"]) - - assert game.root.children[1].children[0].children[0].parent.label == "0" - assert game.root.children[0].children[0].children[0].parent.label == "00" - assert len(game.root.children[1].children[0].children) == 3 - assert len(game.root.children[0].children[0].children) == 3 - - def test_append_move_actions_list_of_mixed_node_references(self): - """Test that nodes from a list of nodes with either 'node' or str references - are resolved correctly. - """ - game = self.sample_extensive_game - game.add_player("Player 3") - - game.root.children[1].children[0].label = " 000" - node_references = [" 000", - game.root.children[0].children[0]] - game.append_move(node_references, "Player 3", ["B", "F", "S"]) - - assert game.root.children[1].children[0].children[0].parent.label == " 000" - assert len(game.root.children[1].children[0].children) == 3 - assert len(game.root.children[0].children[0].children) == 3 - - def test_append_move_labels_list_of_nodes(self): - """Test that nodes from a list of nodes that resolved in the same infoset - have the same labels per action. - """ - game = self.sample_extensive_game - game.add_player("Player 3") - nodes = [game.root.children[1].children[0], - game.root.children[0].children[0]] - game.append_move(nodes, "Player 3", ["B", "F", "S"]) - - action_list = game.players["Player 3"].infosets[0].actions - tmp1 = game.root.children[1].children[0].infoset.actions - tmp2 = game.root.children[0].children[0].infoset.actions - - for (action, action1, action2) in zip(action_list, tmp1, tmp2): - assert action.label == action1.label - assert action.label == action2.label - - def test_append_move_node_list_with_non_terminal_node(self): - """Test that we get an UndefinedOperationError when we import in append_move a list - of nodes that has a non terminal node. - """ - game = self.sample_extensive_game - game.add_player("Player 3") - - self.assertRaises(pygambit.UndefinedOperationError, - game.append_move, - [game.root.children[1], - game.root.children[0].children[1]], - "Player 3", ["B", "F"]) - - def test_append_move_node_list_with_duplicate_node_references(self): - """Test that we get a ValueError when we import in append_move a list - nodes with non-unique node references. - """ - game = games.read_from_file("sample_extensive_game.efg") - game.add_player("Player 3") - game.root.children[0].children[1].label = "00" - - self.assertRaises(ValueError, game.append_move, - ["00", - game.root.children[1].children[0], - game.root.children[0].children[1]], - "Player 3", ["B", "F"]) - - def test_append_move_node_list_is_empty(self): - """Test that we get a ValueError when we import in append_move an - empty list of nodes. - """ - game = games.read_from_file("sample_extensive_game.efg") - game.add_player("Player 3") - - self.assertRaises(ValueError, game.append_move, - [], "Player 3", ["B", "F"]) - - def test_append_infoset_node_list_with_non_terminal_node(self): - """Test that we get an UndefinedOperationError when we import in append_infoset - a list of nodes that has a non terminal node. - """ - game = self.sample_extensive_game - game.add_player("Player 3") - game.append_move(game.root.children[0].children[0], "Player 3", ["B", "F"]) - - self.assertRaises(pygambit.UndefinedOperationError, - game.append_infoset, - [game.root.children[1], - game.root.children[0].children[1]], - game.root.children[0].children[0].infoset) - - def test_append_infoset_node_list_with_duplicate_node(self): - """Test that we get a ValueError when we import in append_infoset a list - with non unique elements. - """ - game = games.read_from_file("sample_extensive_game.efg") - game.add_player("Player 3") - game.append_move(game.root.children[0].children[0], "Player 3", ["B", "F"]) - - self.assertRaises(ValueError, game.append_infoset, - [game.root.children[0].children[1], - game.root.children[1].children[0], - game.root.children[0].children[1]], - game.root.children[0].children[0].infoset) - - def test_append_infoset_node_list_is_empty(self): - """Test that we get a ValueError when we import in append_infoset an - empty list of nodes. - """ - game = games.read_from_file("sample_extensive_game.efg") - game.add_player("Player 3") - game.append_move(game.root.children[0].children[0], "Player 3", ["B", "F"]) - - self.assertRaises(ValueError, game.append_infoset, - [], game.root.children[0].children[0].infoset) + +def test_append_infoset_node_list_is_empty(): + """Test that we get a ValueError when we import in append_infoset an + empty list of nodes. + """ + game = games.read_from_file("sample_extensive_game.efg") + game.add_player("Player 3") + game.append_move(game.root.children[0].children[0], "Player 3", ["B", "F"]) + with pytest.raises(ValueError): + game.append_infoset([], game.root.children[0].children[0].infoset) From 15d17d7b4fe78a28d045023bb6ae6215e06cbe70 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Dec 2024 01:12:21 +0000 Subject: [PATCH 38/99] Bump jidicula/clang-format-action from 4.13.0 to 4.14.0 Bumps [jidicula/clang-format-action](https://github.com/jidicula/clang-format-action) from 4.13.0 to 4.14.0. - [Release notes](https://github.com/jidicula/clang-format-action/releases) - [Commits](https://github.com/jidicula/clang-format-action/compare/v4.13.0...v4.14.0) --- updated-dependencies: - dependency-name: jidicula/clang-format-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 80b7611ea..68eeac632 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.13.0 + uses: jidicula/clang-format-action@v4.14.0 with: clang-format-version: '17' check-path: 'src' From e85f8d8d2e3c721b26c2353fc6dd24a51cffcb04 Mon Sep 17 00:00:00 2001 From: Ted Turocy Date: Tue, 10 Dec 2024 12:46:51 +0000 Subject: [PATCH 39/99] Migrate List to be a thin wrapper of std::list This reimplements the Gambit List class to be a thin wrapper around STL's std::list. The only remaining non-STL operation is to be able to index using the [] operator, which is 1-based. Future new code should use std::list directly, and existing code using List because of [] should be refactored to be able to use std::list. --- src/core/list.h | 476 +++++----------------------- src/pygambit/gambit.pxd | 4 +- src/pygambit/nash.pxi | 12 +- src/solvers/enummixed/enummixed.cc | 6 +- src/solvers/enumpoly/behavextend.cc | 8 +- src/solvers/enumpoly/gpartltr.h | 2 +- src/solvers/enumpoly/gpartltr.imp | 22 +- src/solvers/enumpoly/gpoly.imp | 135 ++++---- src/solvers/enumpoly/gpolylst.imp | 124 ++++---- src/solvers/enumpoly/gsolver.imp | 36 ++- src/solvers/enumpoly/gtree.imp | 23 +- src/solvers/enumpoly/poly.imp | 84 ++--- src/solvers/enumpoly/quiksolv.imp | 2 +- src/solvers/enumpoly/rectangl.imp | 2 +- src/solvers/lcp/efglcp.cc | 2 +- src/solvers/lcp/nfglcp.cc | 2 +- 16 files changed, 284 insertions(+), 656 deletions(-) diff --git a/src/core/list.h b/src/core/list.h index 7b0df7181..1281e7e3e 100644 --- a/src/core/list.h +++ b/src/core/list.h @@ -3,7 +3,7 @@ // Copyright (c) 1994-2024, The Gambit Project (http://www.gambit-project.org) // // FILE: src/libgambit/list.h -// A generic (doubly) linked-list container class +// 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 @@ -23,432 +23,108 @@ #ifndef LIBGAMBIT_LIST_H #define LIBGAMBIT_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 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 diff --git a/src/pygambit/gambit.pxd b/src/pygambit/gambit.pxd index 265eb682c..f3bde61a7 100644 --- a/src/pygambit/gambit.pxd +++ b/src/pygambit/gambit.pxd @@ -32,7 +32,7 @@ cdef extern from "core/array.h": bint operator==(iterator) bint operator!=(iterator) T getitem "operator[]"(int) except + - int Length() except + + int size() except + Array() except + Array(int) except + iterator begin() except + @@ -42,7 +42,7 @@ cdef extern from "core/array.h": 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": diff --git a/src/pygambit/nash.pxi b/src/pygambit/nash.pxi index 678d74475..e9dd6ea47 100644 --- a/src/pygambit/nash.pxi +++ b/src/pygambit/nash.pxi @@ -32,7 +32,7 @@ def _convert_mspd( inlist: c_List[c_MixedStrategyProfileDouble] ) -> typing.List[MixedStrategyProfileDouble]: return [MixedStrategyProfileDouble.wrap(copyitem_list_mspd(inlist, i+1)) - for i in range(inlist.Length())] + for i in range(inlist.size())] @cython.cfunc @@ -40,7 +40,7 @@ def _convert_mspr( inlist: c_List[c_MixedStrategyProfileRational] ) -> typing.List[MixedStrategyProfileRational]: return [MixedStrategyProfileRational.wrap(copyitem_list_mspr(inlist, i+1)) - for i in range(inlist.Length())] + for i in range(inlist.size())] @cython.cfunc @@ -48,7 +48,7 @@ def _convert_mbpd( inlist: c_List[c_MixedBehaviorProfileDouble] ) -> typing.List[MixedBehaviorProfileDouble]: return [MixedBehaviorProfileDouble.wrap(copyitem_list_mbpd(inlist, i+1)) - for i in range(inlist.Length())] + for i in range(inlist.size())] @cython.cfunc @@ -56,7 +56,7 @@ def _convert_mbpr( inlist: c_List[c_MixedBehaviorProfileRational] ) -> typing.List[MixedBehaviorProfileRational]: 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]: @@ -281,7 +281,7 @@ def _logit_strategy_branch(game: Game, 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.Length())] + for i in range(solns.size())] @cython.cclass @@ -367,7 +367,7 @@ def _logit_behavior_branch(game: Game, max_accel: float): solns = LogitBehaviorPrincipalBranchWrapper(game.game, maxregret, first_step, max_accel) ret = [] - for i in range(solns.Length()): + for i in range(solns.size()): p = LogitQREMixedBehaviorProfile() p.thisptr = copyitem_list_qreb(solns, i+1) ret.append(p) diff --git a/src/solvers/enummixed/enummixed.cc b/src/solvers/enummixed/enummixed.cc index 439fbbf77..672c3cc58 100644 --- a/src/solvers/enummixed/enummixed.cc +++ b/src/solvers/enummixed/enummixed.cc @@ -35,8 +35,8 @@ List>> EnumMixedStrategySolution::GetCliques() c { if (m_cliques1.empty()) { // Cliques are generated on demand - int n = m_node1.Length(); - if (m_node2.Length() != n) { + int n = m_node1.size(); + if (m_node2.size() != n) { throw DimensionException(); } @@ -52,7 +52,7 @@ List>> EnumMixedStrategySolution::GetCliques() c } List>> solution; - for (int cl = 1; cl <= m_cliques1.Length(); cl++) { + for (int cl = 1; cl <= m_cliques1.size(); cl++) { solution.push_back(List>()); for (int i = 1; i <= m_cliques1[cl].Length(); i++) { for (int j = 1; j <= m_cliques2[cl].Length(); j++) { diff --git a/src/solvers/enumpoly/behavextend.cc b/src/solvers/enumpoly/behavextend.cc index 9d7aaf2b4..83d08d8a7 100644 --- a/src/solvers/enumpoly/behavextend.cc +++ b/src/solvers/enumpoly/behavextend.cc @@ -66,7 +66,7 @@ void DeviationInfosets(List &answer, const BehaviorSupportProfile & done = true; } } - answer.Insert(iset, insert); + answer.insert(std::next(answer.begin(), insert - 1), iset); } for (auto action : iset->GetActions()) { @@ -118,7 +118,7 @@ std::list DeviationSupports(const BehaviorSupportProfile BehaviorSupportProfile new_supp(big_supp); - for (int i = 1; i <= isetlist.Length(); i++) { + for (int i = 1; i <= isetlist.size(); i++) { for (int j = 1; j < isetlist[i]->NumActions(); j++) { new_supp.RemoveAction(isetlist[i]->GetAction(j)); } @@ -136,7 +136,7 @@ std::list DeviationSupports(const BehaviorSupportProfile } answer.push_back(new_supp); - int iset_cursor = isetlist.Length(); + int iset_cursor = isetlist.size(); while (iset_cursor > 0) { if (active_act_no[iset_cursor] == 0 || active_act_no[iset_cursor] == isetlist[iset_cursor]->NumActions()) { @@ -146,7 +146,7 @@ std::list DeviationSupports(const BehaviorSupportProfile new_supp.RemoveAction(isetlist[iset_cursor]->GetAction(active_act_no[iset_cursor])); active_act_no[iset_cursor]++; new_supp.AddAction(isetlist[iset_cursor]->GetAction(active_act_no[iset_cursor])); - for (int k = iset_cursor + 1; k <= isetlist.Length(); k++) { + for (int k = iset_cursor + 1; k <= isetlist.size(); k++) { if (active_act_no[k] > 0) { new_supp.RemoveAction(isetlist[k]->GetAction(1)); } diff --git a/src/solvers/enumpoly/gpartltr.h b/src/solvers/enumpoly/gpartltr.h index c111aa1e3..6c9ad5046 100644 --- a/src/solvers/enumpoly/gpartltr.h +++ b/src/solvers/enumpoly/gpartltr.h @@ -103,7 +103,7 @@ template class ListOfPartialTrees { const TreeOfPartials &operator[](int i) const { return PartialTreeList[i]; } // Information - int Length() const { return PartialTreeList.Length(); } + int Length() const { return PartialTreeList.size(); } int Dmnsn() const { return PartialTreeList.front().Dmnsn(); } Gambit::Matrix DerivativeMatrix(const Gambit::Vector &) const; Gambit::Matrix DerivativeMatrix(const Gambit::Vector &, const int &) const; diff --git a/src/solvers/enumpoly/gpartltr.imp b/src/solvers/enumpoly/gpartltr.imp index 32a236505..18472ec43 100644 --- a/src/solvers/enumpoly/gpartltr.imp +++ b/src/solvers/enumpoly/gpartltr.imp @@ -94,7 +94,7 @@ T TreeOfPartials::MaximalNonconstantContributionRECURSIVE( if (n->GetEldest() != nullptr) { Gambit::List> *> children = PartialTree.Children(n); - for (int i = 1; i <= children.Length(); i++) { + for (int i = 1; i <= children.size(); i++) { wrtos[i]++; T increment = children[i]->GetData().Evaluate(p); @@ -130,7 +130,7 @@ T TreeOfPartials::MaximalNonconstantDifferenceRECURSIVE( if (n1->GetEldest() != nullptr && n2->GetEldest() != nullptr) { Gambit::List> *> children1 = PartialTree.Children(n1); Gambit::List> *> children2 = PartialTree.Children(n2); - for (int i = 1; i <= children1.Length(); i++) { + for (int i = 1; i <= children1.size(); i++) { wrtos[i]++; T increment = children1[i]->GetData().Evaluate(p) - children2[i]->GetData().Evaluate(p); @@ -155,8 +155,8 @@ T TreeOfPartials::MaximalNonconstantDifferenceRECURSIVE( } else if (n1->GetEldest() != nullptr && n2->GetEldest() == nullptr) { - Gambit::List> *> children1 = PartialTree.Children(n1); - for (int i = 1; i <= children1.Length(); i++) { + auto children1 = PartialTree.Children(n1); + for (int i = 1; i <= children1.size(); i++) { wrtos[i]++; T increment = children1[i]->GetData().Evaluate(p); @@ -180,8 +180,8 @@ T TreeOfPartials::MaximalNonconstantDifferenceRECURSIVE( } else if (n1->GetEldest() == nullptr && n2->GetEldest() != nullptr) { - Gambit::List> *> children2 = PartialTree.Children(n2); - for (int i = 1; i <= children2.Length(); i++) { + auto children2 = PartialTree.Children(n2); + for (int i = 1; i <= children2.size(); i++) { wrtos[i]++; T increment = children2[i]->GetData().Evaluate(p); @@ -396,16 +396,14 @@ template bool TreeOfPartials::PolyEverywhereNegativeIn(const gRecta // Constructors / Destructors //--------------------------- -template -ListOfPartialTrees::ListOfPartialTrees(const Gambit::List> &given) : PartialTreeList() +template ListOfPartialTrees::ListOfPartialTrees(const Gambit::List> &given) { - for (int i = 1; i <= given.Length(); i++) { - PartialTreeList.push_back(TreeOfPartials(given[i])); + for (const auto &poly : given) { + PartialTreeList.push_back(TreeOfPartials(poly)); } } -template -ListOfPartialTrees::ListOfPartialTrees(const gPolyList &given) : PartialTreeList() +template ListOfPartialTrees::ListOfPartialTrees(const gPolyList &given) { for (int i = 1; i <= given.Length(); i++) { PartialTreeList.push_back(TreeOfPartials(given[i])); diff --git a/src/solvers/enumpoly/gpoly.imp b/src/solvers/enumpoly/gpoly.imp index adc78c1e2..6d0ab3ca5 100644 --- a/src/solvers/enumpoly/gpoly.imp +++ b/src/solvers/enumpoly/gpoly.imp @@ -129,12 +129,12 @@ template gPoly &gPoly::operator=(const std::string &Hold) } Gambit::List> newTerms; - for (int j = 1; j <= Terms.Length(); j++) { + for (int j = 1; j <= Terms.size(); j++) { int low = 0; - int high = newTerms.Length() + 1; + int high = newTerms.size() + 1; while (low + 1 < high) { int test = low + (high - low) / 2; - if (1 <= test && test <= newTerms.Length()) { + if (1 <= test && test <= newTerms.size()) { // assert (Terms[j].ExpV() != newTerms[test].ExpV()); } if (Order->Less(Terms[j].ExpV(), newTerms[test].ExpV())) { @@ -144,7 +144,7 @@ template gPoly &gPoly::operator=(const std::string &Hold) low = test; } } - newTerms.Insert(Terms[j], high); + newTerms.insert(std::next(newTerms.begin(), high - 1), Terms[j]); } Terms = newTerms; @@ -154,7 +154,7 @@ template gPoly &gPoly::operator=(const std::string &Hold) template gPoly gPoly::operator-() const { gPoly neg(*this); - for (int j = 1; j <= Terms.Length(); j++) { + for (int j = 1; j <= Terms.size(); j++) { neg.Terms[j] = -Terms[j]; } return neg; @@ -172,7 +172,7 @@ template void gPoly::operator-=(const gPoly &p) // assert(Space == p.Space); gPoly neg = p; - for (int i = 1; i <= neg.Terms.Length(); i++) { + for (int i = 1; i <= neg.Terms.size(); i++) { neg.Terms[i] = -neg.Terms[i]; } Terms = Adder(Terms, neg.Terms); @@ -227,19 +227,17 @@ template void gPoly::operator*=(const gPoly &p) template void gPoly::operator*=(const T &val) { - for (int j = 1; j <= Terms.Length(); j++) { + for (int j = 1; j <= Terms.size(); j++) { Terms[j] *= val; } } template bool gPoly::operator==(const gPoly &p) const { - // assert(Space == p.Space && Order == p.Order); - - if (Terms.Length() != p.Terms.Length()) { + if (Terms.size() != p.Terms.size()) { return false; } - if (Terms.Length() == 0 && p.Terms.Length() == 0) { + if (Terms.size() == 0 && p.Terms.size() == 0) { return true; } @@ -429,7 +427,7 @@ template int gPoly::Dmnsn() const { return Space->Dmnsn(); } template int gPoly::DegreeOfVar(int var_no) const { int max = 0; - for (int j = 1; j <= Terms.Length(); j++) { + for (int j = 1; j <= Terms.size(); j++) { if (max < Terms[j].ExpV()[var_no]) { max = Terms[j].ExpV()[var_no]; } @@ -437,20 +435,12 @@ template int gPoly::DegreeOfVar(int var_no) const return max; } -template bool gPoly::IsZero() const -{ - if (Terms.Length() == 0) { - return true; - } - else { - return false; - } -} +template bool gPoly::IsZero() const { return Terms.empty(); } template int gPoly::Degree() const { int max = 0; - for (int j = 1; j <= Terms.Length(); j++) { + for (int j = 1; j <= Terms.size(); j++) { if (Terms[j].TotalDegree() > max) { max = Terms[j].TotalDegree(); } @@ -465,7 +455,7 @@ template T gPoly::GetCoef(const Gambit::Array &Powers) const template T gPoly::GetCoef(const exp_vect &Powers) const { - for (int j = 1; j <= Terms.Length(); j++) { + for (int j = 1; j <= Terms.size(); j++) { if (Terms[j].ExpV() == Powers) { return Terms[j].Coef(); } @@ -475,10 +465,8 @@ template T gPoly::GetCoef(const exp_vect &Powers) const template T gPoly::NumLeadCoeff() const { - // assert (Degree() == 0 && Terms.Length() <= 1); - - if (Terms.Length() == 1) { - return Terms[1].Coef(); + if (Terms.size() == 1) { + return Terms.front().Coef(); } else { return (T)0; @@ -487,8 +475,8 @@ template T gPoly::NumLeadCoeff() const template bool gPoly::IsConstant() const { - for (int i = 1; i <= Terms.Length(); i++) { - if (!Terms[i].IsConstant()) { + for (const auto &term : Terms) { + if (!term.IsConstant()) { return false; } } @@ -497,8 +485,8 @@ template bool gPoly::IsConstant() const template bool gPoly::IsMultiaffine() const { - for (int i = 1; i <= Terms.Length(); i++) { - if (!Terms[i].IsMultiaffine()) { + for (const auto &term : Terms) { + if (!term.IsMultiaffine()) { return false; } } @@ -508,8 +496,8 @@ template bool gPoly::IsMultiaffine() const template T gPoly::Evaluate(const Gambit::Array &values) const { T answer = 0; - for (int j = 1; j <= Terms.Length(); j++) { - answer += Terms[j].Evaluate(values); + for (const auto &term : Terms) { + answer += term.Evaluate(values); } return answer; } @@ -517,26 +505,21 @@ template T gPoly::Evaluate(const Gambit::Array &values) const template Gambit::List gPoly::ExponentVectors() const { Gambit::List result; - for (int j = 1; j <= Terms.Length(); j++) { - result.push_back(exp_vect(Terms[j].ExpV())); + for (const auto &v : Terms) { + result.push_back(exp_vect(v.ExpV())); } return result; } template Gambit::List> gPoly::MonomialList() const { return Terms; } -template int gPoly::No_Monomials() const -{ - // gout << "Eliminate old code in No_monomials, if successful.\n"; - - return Terms.Length(); -} +template int gPoly::No_Monomials() const { return Terms.size(); } template int gPoly::UniqueActiveVariable() const { Gambit::List ExpVecs = ExponentVectors(); int activar = 0; - for (int i = 1; i <= ExpVecs.Length(); i++) { + for (int i = 1; i <= ExpVecs.size(); i++) { for (int j = 1; j <= Dmnsn(); j++) { if (ExpVecs[i][j] > 0) { if (activar > 0 && activar != j) { @@ -562,7 +545,7 @@ template polynomial gPoly::UnivariateEquivalent(int activar) con coefs.push_back((T)0); } - for (int i = 1; i <= Terms.Length(); i++) { + for (int i = 1; i <= Terms.size(); i++) { coefs[Terms[i].ExpV()[activar] + 1] = Terms[i].Coef(); } } @@ -578,10 +561,10 @@ template Gambit::List> gPoly::Adder(const Gambit::List> &One, const Gambit::List> &Two) const { - if (One.Length() == 0) { + if (One.empty()) { return Two; } - if (Two.Length() == 0) { + if (Two.empty()) { return One; } @@ -589,12 +572,12 @@ Gambit::List> gPoly::Adder(const Gambit::List> &One, int i = 1; int j = 1; - while (i <= One.Length() || j <= Two.Length()) { - if (i > One.Length()) { + while (i <= One.size() || j <= Two.size()) { + if (i > One.size()) { answer.push_back(Two[j]); j++; } - else if (j > Two.Length()) { + else if (j > Two.size()) { answer.push_back(One[i]); i++; } @@ -626,27 +609,27 @@ Gambit::List> gPoly::Mult(const Gambit::List> &One, { Gambit::List> answer; - if (One.Length() == 0 || Two.Length() == 0) { + if (One.empty() || Two.empty()) { return answer; } int i; - for (i = 1; i <= One.Length(); i++) { - for (int j = 1; j <= Two.Length(); j++) { + for (i = 1; i <= One.size(); i++) { + for (int j = 1; j <= Two.size(); j++) { gMono next = One[i] * Two[j]; - if (answer.Length() == 0) { + if (answer.empty()) { answer.push_back(next); } else { int bot = 1; - int top = answer.Length(); + int top = answer.size(); if (Order->Less(answer[top].ExpV(), next.ExpV())) { answer.push_back(next); } else if (Order->Greater(answer[bot].ExpV(), next.ExpV())) { - answer.Insert(next, 1); + answer.push_front(next); } else { if (answer[bot].ExpV() == next.ExpV()) { @@ -673,7 +656,7 @@ Gambit::List> gPoly::Mult(const Gambit::List> &One, answer[bot] += next; } else { - answer.Insert(next, top); + answer.insert(std::next(answer.begin(), top - 1), next); } } } @@ -725,7 +708,7 @@ template gPoly gPoly::EvaluateOneVar(int varnumber, T val) const { gPoly answer(Space, (T)0, Order); - for (int i = 1; i <= Terms.Length(); i++) { + for (int i = 1; i <= Terms.size(); i++) { answer += gPoly(Space, Terms[i].ExpV().AfterZeroingOutExpOfVariable(varnumber), ((T)Terms[i].Coef()) * ((T)pow(val, (double)Terms[i].ExpV()[varnumber])), Order); @@ -739,7 +722,7 @@ exp_vect gPoly::OrderMaxMonomialDivisibleBy(const term_order &order, const ex // gout << "You have just tested OrderMaxMonomialDivisibleBy.\n"; exp_vect answer(Space); // constructs [0,..,0] - for (int i = 1; i <= Terms.Length(); i++) { + for (int i = 1; i <= Terms.size(); i++) { if (order.Less(answer, Terms[i].ExpV()) && answer < Terms[i].ExpV()) { answer = Terms[i].ExpV(); } @@ -751,15 +734,15 @@ template gPoly gPoly::PartialDerivative(int varnumber) const { gPoly newPoly(*this); - for (int i = 1; i <= newPoly.Terms.Length(); i++) { + for (int i = 1; i <= newPoly.Terms.size(); i++) { newPoly.Terms[i] = gMono(newPoly.Terms[i].Coef() * (T)newPoly.Terms[i].ExpV()[varnumber], newPoly.Terms[i].ExpV().AfterDecrementingExpOfVariable(varnumber)); } int j = 1; - while (j <= newPoly.Terms.Length()) { + while (j <= newPoly.Terms.size()) { if (newPoly.Terms[j].Coef() == (T)0) { - newPoly.Terms.Remove(j); + newPoly.Terms.erase(std::next(newPoly.Terms.begin(), j - 1)); } else { j++; @@ -776,7 +759,7 @@ template gPoly gPoly::LeadingCoefficient(int varnumber) const int degree = DegreeOfVar(varnumber); newPoly.Terms = Gambit::List>(); - for (int j = 1; j <= Terms.Length(); j++) { + for (int j = 1; j <= Terms.size(); j++) { if (Terms[j].ExpV()[varnumber] == degree) { newPoly.Terms.push_back( gMono(Terms[j].Coef(), Terms[j].ExpV().AfterZeroingOutExpOfVariable(varnumber))); @@ -795,11 +778,11 @@ template exp_vect gPoly::LeadingPowerProduct(const term_order &orde // assert (Terms.Length() > 0); if (*Order == order) { // worth a try ... - return Terms[Terms.Length()].ExpV(); + return Terms.back().ExpV(); } else { int max = 1; - for (int j = 2; j <= Terms.Length(); j++) { + for (int j = 2; j <= Terms.size(); j++) { if (order.Less(Terms[max].ExpV(), Terms[j].ExpV())) { max = j; } @@ -811,11 +794,11 @@ template exp_vect gPoly::LeadingPowerProduct(const term_order &orde template T gPoly::LeadingCoefficient(const term_order &order) const { if (*Order == order) { // worth a try ... - return Terms[Terms.Length()].Coef(); + return Terms.back().Coef(); } else { int max = 1; - for (int j = 2; j <= Terms.Length(); j++) { + for (int j = 2; j <= Terms.size(); j++) { if (order.Less(Terms[max].ExpV(), Terms[j].ExpV())) { max = j; } @@ -827,11 +810,11 @@ template T gPoly::LeadingCoefficient(const term_order &order) const template gPoly gPoly::LeadingTerm(const term_order &order) const { if (*Order == order) { // worth a try ... - return gPoly(Space, Terms[Terms.Length()], Order); + return gPoly(Space, Terms.back(), Order); } else { int max = 1; - for (int j = 2; j <= Terms.Length(); j++) { + for (int j = 2; j <= Terms.size(); j++) { if (order.Less(Terms[max].ExpV(), Terms[j].ExpV())) { max = j; } @@ -904,7 +887,7 @@ gPoly gPoly::TranslateOfMono(const gMono &m, const Gambit::Vector &n template gPoly gPoly::TranslateOfPoly(const Gambit::Vector &new_origin) const { gPoly answer(GetSpace(), GetOrder()); - for (int i = 1; i <= this->MonomialList().Length(); i++) { + for (int i = 1; i <= this->MonomialList().size(); i++) { answer += TranslateOfMono(this->MonomialList()[i], new_origin); } return answer; @@ -938,7 +921,7 @@ gPoly gPoly::MonoInNewCoordinates(const gMono &m, const Gambit::SquareM template gPoly gPoly::PolyInNewCoordinates(const Gambit::SquareMatrix &M) const { gPoly answer(GetSpace(), GetOrder()); - for (int i = 1; i <= MonomialList().Length(); i++) { + for (int i = 1; i <= MonomialList().size(); i++) { answer += MonoInNewCoordinates(MonomialList()[i], M); } return answer; @@ -947,7 +930,7 @@ template gPoly gPoly::PolyInNewCoordinates(const Gambit::SquareM template T gPoly::MaximalValueOfNonlinearPart(const T &radius) const { T maxcon = (T)0; - for (int i = 1; i <= MonomialList().Length(); i++) { + for (int i = 1; i <= MonomialList().size(); i++) { if (MonomialList()[i].TotalDegree() > 1) { maxcon += MonomialList()[i].Coef() * pow(radius, MonomialList()[i].TotalDegree()); } @@ -981,11 +964,11 @@ template gPoly operator+(const gPoly &poly, const T &val) { retu template void gPoly::Output(std::string &t) const { std::string s; - if (Terms.Length() == 0) { + if (Terms.empty()) { s += "0"; } else { - for (int j = 1; j <= Terms.Length(); j++) { + for (int j = 1; j <= Terms.size(); j++) { if (Terms[j].Coef() < (T)0) { s += "-"; if (j > 1) { @@ -1016,7 +999,7 @@ template void gPoly::Output(std::string &t) const } } - if (j < Terms.Length()) { + if (j < Terms.size()) { s += " "; } } @@ -1043,7 +1026,7 @@ template gPoly TogDouble(const gPoly &given) { gPoly answer(given.GetSpace(), given.GetOrder()); Gambit::List> list = given.MonomialList(); - for (int i = 1; i <= list.Length(); i++) { + for (int i = 1; i <= list.size(); i++) { auto nextcoef = (double)list[i].Coef(); gPoly next(given.GetSpace(), list[i].ExpV(), nextcoef, given.GetOrder()); answer += next; @@ -1056,7 +1039,7 @@ template gPoly NormalizationOfPoly(const gPoly &given) { Gambit::List> list = given.MonomialList(); double maxcoeff = 0.0; - for (int i = 1; i <= list.Length(); i++) { + for (int i = 1; i <= list.size(); i++) { maxcoeff = std::max(maxcoeff, (double)Gambit::abs((double)list[i].Coef())); } @@ -1065,7 +1048,7 @@ template gPoly NormalizationOfPoly(const gPoly &given) } gPoly answer(given.GetSpace(), given.GetOrder()); - for (int i = 1; i <= list.Length(); i++) { + for (int i = 1; i <= list.size(); i++) { double nextcoef = (double)list[i].Coef() / maxcoeff; gPoly next(given.GetSpace(), list[i].ExpV(), nextcoef, given.GetOrder()); answer += next; diff --git a/src/solvers/enumpoly/gpolylst.imp b/src/solvers/enumpoly/gpolylst.imp index 01ab26dc5..30061de1c 100644 --- a/src/solvers/enumpoly/gpolylst.imp +++ b/src/solvers/enumpoly/gpolylst.imp @@ -52,8 +52,7 @@ gPolyList::gPolyList(const gSpace *sp, const term_order *to, const Gambit::List *> &plist) : Space(sp), Order(to), List() { - int ii; - for (ii = 1; ii <= plist.Length(); ii++) { + for (int ii = 1; ii <= plist.size(); ii++) { auto *temp = new gPoly(*plist[ii]); List.push_back(temp); } @@ -63,8 +62,7 @@ template gPolyList::gPolyList(const gSpace *sp, const term_order *to, const Gambit::List> &list) : Space(sp), Order(to), List() { - int ii; - for (ii = 1; ii <= list.Length(); ii++) { + for (int ii = 1; ii <= list.size(); ii++) { auto *temp = new gPoly(list[ii]); List.push_back(temp); } @@ -73,8 +71,7 @@ gPolyList::gPolyList(const gSpace *sp, const term_order *to, const Gambit::Li template gPolyList::gPolyList(const gPolyList &lst) : Space(lst.Space), Order(lst.Order), List() { - int ii; - for (ii = 1; ii <= lst.List.Length(); ii++) { + for (int ii = 1; ii <= lst.List.size(); ii++) { auto *temp = new gPoly(*(lst.List[ii])); List.push_back(temp); } @@ -82,8 +79,7 @@ gPolyList::gPolyList(const gPolyList &lst) : Space(lst.Space), Order(lst.O template gPolyList::~gPolyList() { - int ii; - for (ii = 1; ii <= List.Length(); ii++) { + for (int ii = 1; ii <= List.size(); ii++) { delete List[ii]; } } @@ -94,16 +90,13 @@ template gPolyList::~gPolyList() template gPolyList &gPolyList::operator=(const gPolyList &rhs) { - // assert (Space == rhs.Space && Order == rhs.Order); - if (*this != rhs) { - int ii; - for (ii = List.Length(); ii >= 1; ii--) { + for (int ii = List.size(); ii >= 1; ii--) { delete List[ii]; - List.Remove(ii); + List.erase(std::next(List.begin(), ii - 1)); } - for (ii = 1; ii <= rhs.List.Length(); ii++) { + for (int ii = 1; ii <= rhs.List.size(); ii++) { auto *temp = new gPoly(*(rhs.List[ii])); List.push_back(temp); } @@ -116,10 +109,10 @@ template bool gPolyList::operator==(const gPolyList &rhs) const if (Space != rhs.Space || Order != rhs.Order) { return false; } - if (List.Length() != rhs.List.Length()) { + if (List.size() != rhs.List.size()) { return false; } - for (int j = 1; j <= List.Length(); j++) { + for (int j = 1; j <= List.size(); j++) { if (*List[j] != *(rhs.List[j])) { return false; } @@ -140,11 +133,9 @@ template void gPolyList::operator+=(const gPoly &new_poly) template void gPolyList::operator+=(const gPolyList &new_list) { - Gambit::List *> temp; for (int i = 1; i <= new_list.Length(); i++) { - temp.push_back(new gPoly(new_list[i])); + List.push_back(new gPoly(new_list[i])); } - List += temp; } // NB - does not copy pointee - see gpolylst.h @@ -172,7 +163,7 @@ template bool gPolyList::SelfReduction(const int &target, const ter while (!tear_up.IsZero()) { int index = 1; - while (index <= List.Length() && !tear_up.IsZero()) { + while (index <= List.size() && !tear_up.IsZero()) { if (index == target || List[index]->IsZero()) { index++; } @@ -206,7 +197,7 @@ gPoly gPolyList::ReductionOf(const gPoly &f, const term_order &order) c while (!tear_up.IsZero()) { int index = 1; - while (index <= List.Length() && !tear_up.IsZero()) { + while (index <= List.size() && !tear_up.IsZero()) { if (List[index]->IsZero()) { index++; } @@ -231,13 +222,13 @@ template void gPolyList::Sort(const term_order &order) // bubble sort, justified since // I expect List.Length() < 10 { - if (List.Length() <= 1) { + if (List.size() <= 1) { return; } int ii; - for (ii = 1; ii < List.Length(); ii++) { + for (ii = 1; ii < List.size(); ii++) { if (!List[ii]->IsZero()) { - for (int j = ii + 1; j <= List.Length(); j++) { + for (int j = ii + 1; j <= List.size(); j++) { bool swap = false; if (List[j]->IsZero()) { swap = true; @@ -262,19 +253,19 @@ void gPolyList::CriterionTwo(Gambit::List &uncomputed, const term_order &order) const { for (int ell = 1; ell < no_polys; ell++) { - int spot = uncomputed.Find(index_pair(ell, no_polys)); - if (spot != 0) { - int ii; - for (ii = 1; ii < no_polys; ii++) { - if (ii != ell && spot != 0) { - if (uncomputed.Contains(index_pair(ii, ell)) || computed.Contains(index_pair(ii, ell))) { - if (uncomputed.Contains(index_pair(ii, no_polys))) { + auto spot = std::find(uncomputed.begin(), uncomputed.end(), index_pair(ell, no_polys)); + if (spot != uncomputed.end()) { + for (int ii = 1; ii < no_polys; ii++) { + if (ii != ell && spot != uncomputed.end()) { + if (contains(uncomputed, index_pair(ii, ell)) || + contains(uncomputed, index_pair(ii, ell))) { + if (contains(uncomputed, index_pair(ii, no_polys))) { exp_vect lpp_i = List[ii]->LeadingPowerProduct(order); exp_vect lpp_ell = List[ell]->LeadingPowerProduct(order); exp_vect lpp_no_polys = List[no_polys]->LeadingPowerProduct(order); if (lpp_ell.Divides(lpp_i.LCM(lpp_no_polys))) { - uncomputed.Remove(spot); - spot = 0; + uncomputed.erase(spot); + spot = uncomputed.end(); } } } @@ -282,17 +273,16 @@ void gPolyList::CriterionTwo(Gambit::List &uncomputed, } } } - int ii; - for (ii = 1; ii < no_polys; ii++) { - if (uncomputed.Contains(index_pair(ii, no_polys))) { + for (int ii = 1; ii < no_polys; ii++) { + if (contains(uncomputed, index_pair(ii, no_polys))) { for (int j = ii + 1; j < no_polys; j++) { - int spot = uncomputed.Find(index_pair(ii, j)); - if (uncomputed.Contains(index_pair(j, no_polys)) && (spot != 0)) { + auto spot = std::find(uncomputed.begin(), uncomputed.end(), index_pair(ii, j)); + if (contains(uncomputed, index_pair(j, no_polys)) && spot != uncomputed.end()) { exp_vect lpp_i = List[ii]->LeadingPowerProduct(order); exp_vect lpp_j = List[j]->LeadingPowerProduct(order); exp_vect lpp_no_polys = List[no_polys]->LeadingPowerProduct(order); if (lpp_no_polys.Divides(lpp_i.LCM(lpp_j))) { - uncomputed.Remove(spot); + uncomputed.erase(spot); } } } @@ -303,37 +293,37 @@ void gPolyList::CriterionTwo(Gambit::List &uncomputed, template void gPolyList::Grobnerize(const term_order &order) { int index = 1; // Remove all 0's from List - while (index <= List.Length()) { + while (index <= List.size()) { if (List[index]->IsZero()) { delete List[index]; - List.Remove(index); + List.erase(std::next(List.begin(), index - 1)); } else { index++; } } - if (List.Length() <= 1) { + if (List.size() <= 1) { return; } int ii; - for (ii = 1; ii <= List.Length(); ii++) { + for (ii = 1; ii <= List.size(); ii++) { List[ii]->ToMonic(order); } Gambit::List uncomputed; Gambit::List computed; - for (ii = 2; ii <= List.Length(); ii++) { + for (ii = 2; ii <= List.size(); ii++) { for (int j = 1; j < ii; j++) { uncomputed.push_back(index_pair(j, ii)); } CriterionTwo(uncomputed, computed, ii, order); } - while (uncomputed.Length() > 0) { + while (!uncomputed.empty()) { int mindex = 1; - for (ii = 2; ii <= uncomputed.Length(); ii++) { + for (ii = 2; ii <= uncomputed.size(); ii++) { if (order.Less(List[uncomputed[ii][1]]->LeadingPowerProduct(order).LCM( List[uncomputed[ii][2]]->LeadingPowerProduct(order)), List[uncomputed[mindex][1]]->LeadingPowerProduct(order).LCM( @@ -344,7 +334,7 @@ template void gPolyList::Grobnerize(const term_order &order) computed.push_back(uncomputed[mindex]); int ii = uncomputed[mindex][1]; int j = uncomputed[mindex][2]; - uncomputed.Remove(mindex); + uncomputed.erase(std::next(uncomputed.begin(), mindex - 1)); if (!List[ii]->LeadingPowerProduct(order).UsesDifferentVariablesThan( List[j]->LeadingPowerProduct(order))) { @@ -353,10 +343,10 @@ template void gPolyList::Grobnerize(const term_order &order) h.ToMonic(order); auto *hptr = new gPoly(h); List.push_back(hptr); - for (int k = 1; k < List.Length(); k++) { - uncomputed.push_back(index_pair(k, List.Length())); + for (int k = 1; k < List.size(); k++) { + uncomputed.push_back(index_pair(k, List.size())); } - CriterionTwo(uncomputed, computed, List.Length(), order); + CriterionTwo(uncomputed, computed, List.size(), order); } } } @@ -374,12 +364,12 @@ template void gPolyList::GrobnerToMinimalGrobner(const term_order & if (List[i]->LeadingPowerProduct(order) <= List[j]->LeadingPowerProduct(order)) { delete List[j]; - List.Remove(j); + List.erase(std::next(List.begin(), j - 1)); } else if (List[i]->LeadingPowerProduct(order) >= List[j]->LeadingPowerProduct(order)) { delete List[i]; - List.Remove(i); + List.erase(std::next(List.begin(), i - 1)); if (i < j - 1) { j--; } @@ -407,11 +397,11 @@ template void gPolyList::MinimalGrobnerToReducedGrobner(const term_ } int i = 1; - while (i <= List.Length()) { + while (i <= List.size()) { gPolyList AllBut_ith(*this); delete AllBut_ith.List[i]; - AllBut_ith.List.Remove(i); + AllBut_ith.List.erase(std::next(AllBut_ith.List.begin(), i - 1)); gPoly h = AllBut_ith.ReductionOf(*List[i], order); delete List[i]; @@ -475,17 +465,16 @@ template gPolyList gPolyList::InteriorSegment(int first, int las template Gambit::List> gPolyList::UnderlyingList() const { Gambit::List> NewList; - int ii; - for (ii = 1; ii <= List.Length(); ii++) { - NewList.push_back(*List[ii]); + for (const auto &v : List) { + NewList.push_back(*v); } return NewList; } template bool gPolyList::IsMultiaffine() const { - for (int i = 1; i <= List.Length(); i++) { - if (!(*List[i]).IsMultiaffine()) { + for (const auto &v : List) { + if (!(*v).IsMultiaffine()) { return false; } } @@ -495,8 +484,7 @@ template bool gPolyList::IsMultiaffine() const template Gambit::Vector gPolyList::Evaluate(const Gambit::Vector &v) const { Gambit::Vector answer(Length()); - int ii; - for (ii = 1; ii <= List.Length(); ii++) { + for (int ii = 1; ii <= List.size(); ii++) { answer[ii] = List[ii]->Evaluate(v); } @@ -505,7 +493,7 @@ template Gambit::Vector gPolyList::Evaluate(const Gambit::Vector template bool gPolyList::IsRoot(const Gambit::Vector &v) const { - for (int ii = 1; ii <= List.Length(); ii++) { + for (int ii = 1; ii <= List.size(); ii++) { if (List[ii]->Evaluate(v) != (T)0) { return false; } @@ -531,7 +519,7 @@ template gPoly gPolyList::DetOfDerivativeMatrix() const { // assert(List.Length() == Space->Dmnsn()); - int n = List.Length(); + int n = List.size(); Gambit::RectArray *> deriv_matrix = DerivativeMatrix(); gPoly answer(Space, Order); @@ -588,8 +576,7 @@ Gambit::SquareMatrix gPolyList::SquareDerivativeMatrix(const Gambit::Vecto template Gambit::List> gPolyList::ListTogDouble() const { Gambit::List> newlist; - int ii; - for (ii = 1; ii <= List.Length(); ii++) { + for (int ii = 1; ii <= List.size(); ii++) { newlist.push_back(gPoly(TogDouble(*List[ii]))); } return newlist; @@ -598,14 +585,13 @@ template Gambit::List> gPolyList::ListTogDouble() con template Gambit::List> gPolyList::NormalizedList() const { Gambit::List> newlist; - int ii; - for (ii = 1; ii <= List.Length(); ii++) { - newlist.push_back(gPoly(NormalizationOfPoly(*List[ii]))); + for (const auto &v : List) { + newlist.push_back(gPoly(NormalizationOfPoly(*v))); } return newlist; } template const gSpace *gPolyList::AmbientSpace() const { return Space; } template const term_order *gPolyList::TermOrder() const { return Order; } -template int gPolyList::Length() const { return List.Length(); } +template int gPolyList::Length() const { return List.size(); } template int gPolyList::Dmnsn() const { return Space->Dmnsn(); } diff --git a/src/solvers/enumpoly/gsolver.imp b/src/solvers/enumpoly/gsolver.imp index cbb3b94f2..22d9610c3 100644 --- a/src/solvers/enumpoly/gsolver.imp +++ b/src/solvers/enumpoly/gsolver.imp @@ -79,7 +79,7 @@ gSolver::ContinuationSolutions(const Gambit::List> &list, const // DEBUG // gout << "Just left PreciseRoots ... \n"; - for (int i = 1; i <= rootlist.Length(); i++) { + for (int i = 1; i <= rootlist.size(); i++) { Gambit::Vector newvec(knownvals); newvec[curvar] = rootlist[i]; if (curvar == dmnsn) { @@ -87,20 +87,24 @@ gSolver::ContinuationSolutions(const Gambit::List> &list, const } else { Gambit::List> newlist; - for (int j = 2; j <= list.Length(); j++) { + for (int j = 2; j <= list.size(); j++) { newlist.push_back(list[j].EvaluateOneVar(curvar, rootlist[i])); } - answer += ContinuationSolutions(newlist, dmnsn, curvar + 1, newvec); + for (const auto &soln : ContinuationSolutions(newlist, dmnsn, curvar + 1, newvec)) { + answer.push_back(soln); + } } } } else { // (list[1].IsZero()) Gambit::List> newlist; - for (int j = 2; j <= list.Length(); j++) { + for (int j = 2; j <= list.size(); j++) { newlist.push_back(list[j]); } - answer += ContinuationSolutions(newlist, dmnsn, curvar, knownvals); + for (const auto &soln : ContinuationSolutions(newlist, dmnsn, curvar, knownvals)) { + answer.push_back(soln); + } } return answer; @@ -137,14 +141,14 @@ template Gambit::List> gSolver::Roots() Gambit::List> UnivariateRootEquations; int i; for (i = 1; i <= TheIdeal.Dmnsn(); i++) { - Gambit::Matrix CoefficientMatrix(mon_bss.Length() + 1, mon_bss.Length()); + Gambit::Matrix CoefficientMatrix(mon_bss.size() + 1, mon_bss.size()); int j = 0; bool done = false; while (!done) { exp_vect x_i_to_j(InputList.AmbientSpace(), i, j); gPoly power(InputList.AmbientSpace(), x_i_to_j, (T)1, TheIdeal.Order()); gPoly reduced_power = TheIdeal.CanonicalBasis().ReductionOf(power, *(TheIdeal.Order())); - for (int k = 1; k <= mon_bss.Length(); k++) { + for (int k = 1; k <= mon_bss.size(); k++) { CoefficientMatrix(j + 1, k) = reduced_power.GetCoef(mon_bss[k]); } @@ -152,9 +156,9 @@ template Gambit::List> gSolver::Roots() // gout << "After updating, j = " << j // << "and CoefficientMatrix is\n" << CoefficientMatrix << "\n"; - Gambit::Matrix SubMatrix(j + 1, mon_bss.Length()); + Gambit::Matrix SubMatrix(j + 1, mon_bss.size()); for (int m = 1; m <= j + 1; m++) { - for (int n = 1; n <= mon_bss.Length(); n++) { + for (int n = 1; n <= mon_bss.size(); n++) { SubMatrix(m, n) = CoefficientMatrix(m, n); } } @@ -174,8 +178,8 @@ template Gambit::List> gSolver::Roots() } Gambit::List> ConvertedUnivariateRootEquations; - for (i = 1; i <= UnivariateRootEquations.Length(); i++) { - ConvertedUnivariateRootEquations.push_back(UnivariateRootEquations[i].TogDouble()); + for (const auto &eqn : UnivariateRootEquations) { + ConvertedUnivariateRootEquations.push_back(eqn.TogDouble()); } Gambit::List> original; @@ -189,8 +193,8 @@ template Gambit::List> gSolver::Roots() for (i = 1; i <= TheIdeal.Dmnsn(); i++) { Gambit::List roots_of_eqn_i = ConvertedUnivariateRootEquations[i].PreciseRoots(root_interval, error); - for (int j = 1; j <= original.Length(); j++) { - for (int k = 1; k <= roots_of_eqn_i.Length(); k++) { + for (int j = 1; j <= original.size(); j++) { + for (int k = 1; k <= roots_of_eqn_i.size(); k++) { Gambit::Vector new_vec = original[j]; new_vec[i] = roots_of_eqn_i[k]; revised.push_back(new_vec); @@ -203,9 +207,9 @@ template Gambit::List> gSolver::Roots() Gambit::List> finished; Gambit::List> gDoublePolys = InputList.ListTogDouble(); gPolyList InputListTogDouble(TheIdeal.TheSpace(), TheIdeal.Order(), gDoublePolys); - for (i = 1; i <= original.Length(); i++) { - if (InputListTogDouble.IsRoot(original[i])) { - finished.push_back(original[i]); + for (const auto &v : original) { + if (InputListTogDouble.IsRoot(v)) { + finished.push_back(v); } } return finished; diff --git a/src/solvers/enumpoly/gtree.imp b/src/solvers/enumpoly/gtree.imp index a831b3ee1..eb1081136 100644 --- a/src/solvers/enumpoly/gtree.imp +++ b/src/solvers/enumpoly/gtree.imp @@ -74,13 +74,12 @@ template void gTree::InsertAt(const T &t, gTreeNode *n) template void gTree::RecursiveCopy(gTreeNode *copyn, const gTreeNode *orign) { - Gambit::List *> oldchildren = Children(orign); - int i; - for (i = 1; i <= oldchildren.Length(); i++) { + auto oldchildren = Children(orign); + for (int i = 1; i <= oldchildren.size(); i++) { InsertAt(oldchildren[i]->data, copyn); } - Gambit::List *> newchildren = Children(copyn); - for (i = 1; i <= newchildren.Length(); i++) { + auto newchildren = Children(copyn); + for (int i = 1; i <= newchildren.size(); i++) { RecursiveCopy(newchildren[i], oldchildren[i]); } } @@ -156,12 +155,12 @@ bool gTree::SubtreesAreIsomorphic(const gTreeNode *lhs, const gTreeNode if (lhs->data != rhs->data) { return false; } - Gambit::List *> lchildren = Children(lhs); - Gambit::List *> rchildren = Children(rhs); - if (lchildren.Length() != rchildren.Length()) { + auto lchildren = Children(lhs); + auto rchildren = Children(rhs); + if (lchildren.size() != rchildren.size()) { return false; } - for (int i = 1; i <= lchildren.Length(); i++) { + for (int i = 1; i <= lchildren.size(); i++) { if (!SubtreesAreIsomorphic(lchildren[i], rchildren[i])) { return false; } @@ -171,9 +170,9 @@ bool gTree::SubtreesAreIsomorphic(const gTreeNode *lhs, const gTreeNode template void gTree::RecursiveFlush(const gTreeNode *n) { - Gambit::List *> children = Children(n); - for (int i = 1; i <= children.Length(); i++) { - RecursiveFlush(children[i]); + auto children = Children(n); + for (const auto &child : children) { + RecursiveFlush(child); } delete n; } diff --git a/src/solvers/enumpoly/poly.imp b/src/solvers/enumpoly/poly.imp index 068b52422..3629965f6 100644 --- a/src/solvers/enumpoly/poly.imp +++ b/src/solvers/enumpoly/poly.imp @@ -131,8 +131,8 @@ template polynomial polynomial::operator+(const polynomial &y } } - while ((sum.coeflist.Length() >= 1) && (sum.coeflist[sum.coeflist.Length()] == (T)0)) { - sum.coeflist.Remove(sum.coeflist.Length()); + while (!sum.coeflist.empty() && sum.coeflist.back() == (T)0) { + sum.coeflist.pop_back(); } return sum; @@ -249,7 +249,7 @@ template void polynomial::ToMonic() // assert (!IsZero()); T lc = LeadingCoefficient(); - for (int i = 1; i <= coeflist.Length(); i++) { + for (int i = 1; i <= coeflist.size(); i++) { coeflist[i] /= lc; } } @@ -257,8 +257,8 @@ template void polynomial::ToMonic() template polynomial polynomial::TogDouble() const { Gambit::List newcoefs; - for (int i = 1; i <= coeflist.Length(); i++) { - newcoefs.push_back((double)coeflist[i]); + for (const auto &coef : coeflist) { + newcoefs.push_back((double)coef); } return polynomial(newcoefs); } @@ -267,15 +267,7 @@ template polynomial polynomial::TogDouble() const // information //-------------------------------------------------------------------------- -template bool polynomial::IsZero() const -{ - if (coeflist.Length() == 0) { - return true; - } - else { - return false; - } -} +template bool polynomial::IsZero() const { return coeflist.empty(); } template T polynomial::EvaluationAt(const T &arg) const { @@ -295,7 +287,7 @@ template T polynomial::EvaluationAt(const T &arg) const return answer; } -template int polynomial::Degree() const { return coeflist.Length() - 1; } +template int polynomial::Degree() const { return coeflist.size() - 1; } template T polynomial::LeadingCoefficient() const { @@ -413,12 +405,11 @@ Gambit::List> polynomial::RootSubintervals(const gInterval &I Gambit::List> to_be_processed; to_be_processed.push_back(I); - while (to_be_processed.Length() > 0) { - - gInterval in_process = to_be_processed.Remove(to_be_processed.Length()); + while (!to_be_processed.empty()) { + gInterval in_process = to_be_processed.back(); + to_be_processed.pop_back(); if (EvaluationAt(in_process.LowerBound()) == (T)0) { - if (Df.CannotHaveRootsIn(in_process)) { answer.push_back(in_process); } @@ -427,7 +418,6 @@ Gambit::List> polynomial::RootSubintervals(const gInterval &I to_be_processed.push_back(in_process.LeftHalf()); } } - else if (EvaluationAt(in_process.UpperBound()) == (T)0) { if (Df.CannotHaveRootsIn(in_process)) { if (in_process.UpperBound() == I.UpperBound()) { @@ -464,30 +454,28 @@ gInterval polynomial::NeighborhoodOfRoot(const gInterval &I, T &error) { Gambit::List> intrvls; intrvls.push_back(I); - while (intrvls[intrvls.Length()].Length() >= error) { - if (EvaluationAt(intrvls[intrvls.Length()].LowerBound()) == (T)0) { - intrvls.push_back(gInterval(intrvls[intrvls.Length()].LowerBound(), - intrvls[intrvls.Length()].LowerBound())); + while (intrvls.back().Length() >= error) { + if (EvaluationAt(intrvls.back().LowerBound()) == (T)0) { + intrvls.push_back(gInterval(intrvls.back().LowerBound(), intrvls.back().LowerBound())); } - else if (EvaluationAt(intrvls[intrvls.Length()].UpperBound()) == (T)0) { - intrvls.push_back(gInterval(intrvls[intrvls.Length()].UpperBound(), - intrvls[intrvls.Length()].UpperBound())); + else if (EvaluationAt(intrvls.back().UpperBound()) == (T)0) { + intrvls.push_back(gInterval(intrvls.back().UpperBound(), intrvls.back().UpperBound())); } - else if ((EvaluationAt(intrvls[intrvls.Length()].LowerBound()) >= (T)0 && - EvaluationAt(intrvls[intrvls.Length()].Midpoint()) <= (T)0) || - (EvaluationAt(intrvls[intrvls.Length()].LowerBound()) <= (T)0 && - EvaluationAt(intrvls[intrvls.Length()].Midpoint()) >= (T)0)) { - intrvls.push_back(intrvls[intrvls.Length()].LeftHalf()); + else if ((EvaluationAt(intrvls.back().LowerBound()) >= (T)0 && + EvaluationAt(intrvls.back().Midpoint()) <= (T)0) || + (EvaluationAt(intrvls.back().LowerBound()) <= (T)0 && + EvaluationAt(intrvls.back().Midpoint()) >= (T)0)) { + intrvls.push_back(intrvls.back().LeftHalf()); } else { - intrvls.push_back(intrvls[intrvls.Length()].RightHalf()); + intrvls.push_back(intrvls.back().RightHalf()); } } - return intrvls[intrvls.Length()]; + return intrvls.back(); // It is, perhaps, possible to speed this up, at least for double's // by introducing Newton's method. @@ -500,7 +488,7 @@ Gambit::List> polynomial::PreciseRootIntervals(const gInterval> coarse = RootSubintervals(I); Gambit::List> fine; - for (int i = 1; i <= coarse.Length(); i++) { + for (int i = 1; i <= coarse.size(); i++) { fine.push_back(NeighborhoodOfRoot(coarse[i], error)); } @@ -530,7 +518,7 @@ Gambit::List polynomial::PreciseRoots(const gInterval &I, T &error) con p = p / factor; } Gambit::List> fine = factor.PreciseRootIntervals(I, error); - for (int j = 1; j <= fine.Length(); j++) { + for (int j = 1; j <= fine.size(); j++) { T approx = fine[j].LowerBound(); for (int h = 1; h <= 2; h++) { approx -= factor.EvaluationAt(approx) / @@ -641,8 +629,8 @@ complexpoly complexpoly::operator+(const complexpoly &y) const } } - while ((sum.coeflist.Length() >= 1) && (sum.coeflist[sum.coeflist.Length()] == (gComplex)0)) { - sum.coeflist.Remove(sum.coeflist.Length()); + while (!sum.coeflist.empty() && sum.coeflist.back() == (gComplex)0) { + sum.coeflist.pop_back(); } return sum; @@ -747,7 +735,7 @@ void complexpoly::ToMonic() // assert (!IsZero()); gComplex lc = LeadingCoefficient(); - for (int i = 1; i <= coeflist.Length(); i++) { + for (int i = 1; i <= coeflist.size(); i++) { coeflist[i] /= lc; } } @@ -756,15 +744,7 @@ void complexpoly::ToMonic() // information //-------------------------------------------------------------------------- -bool complexpoly::IsZero() const -{ - if (coeflist.Length() == 0) { - return true; - } - else { - return false; - } -} +bool complexpoly::IsZero() const { return coeflist.empty(); } gComplex complexpoly::EvaluationAt(const gComplex &arg) const { @@ -781,7 +761,7 @@ gComplex complexpoly::EvaluationAt(const gComplex &arg) const return answer; } -int complexpoly::Degree() const { return coeflist.Length() - 1; } +int complexpoly::Degree() const { return coeflist.size() - 1; } gComplex complexpoly::LeadingCoefficient() const { @@ -869,9 +849,11 @@ Gambit::List complexpoly::Roots() const lin_form_coeffs.push_back(gComplex(-1.0, 0.0)); complexpoly linear_form(lin_form_coeffs); complexpoly quotient = *this / linear_form; - answer += quotient.Roots(); + for (const auto &root : quotient.Roots()) { + answer.push_back(root); + } - for (int i = 1; i <= answer.Length(); i++) { // "Polish" each root twice + for (int i = 1; i <= answer.size(); i++) { // "Polish" each root twice answer[i] -= EvaluationAt(answer[i]) / deriv.EvaluationAt(answer[i]); answer[i] -= EvaluationAt(answer[i]) / deriv.EvaluationAt(answer[i]); } diff --git a/src/solvers/enumpoly/quiksolv.imp b/src/solvers/enumpoly/quiksolv.imp index 6adab396a..e409a39dd 100644 --- a/src/solvers/enumpoly/quiksolv.imp +++ b/src/solvers/enumpoly/quiksolv.imp @@ -576,7 +576,7 @@ QuikSolv::FindRootsRecursion(Gambit::List> *rootlistpt } bool already_found = false; - for (i = 1; i <= rootlistptr->Length(); i++) { + for (i = 1; i <= rootlistptr->size(); i++) { if (fuzzy_equals(point, (*rootlistptr)[i])) { already_found = true; } diff --git a/src/solvers/enumpoly/rectangl.imp b/src/solvers/enumpoly/rectangl.imp index 6a74c96df..50fbfa660 100644 --- a/src/solvers/enumpoly/rectangl.imp +++ b/src/solvers/enumpoly/rectangl.imp @@ -78,7 +78,7 @@ template bool gRectangle::operator!=(const gRectangle &rhs) cons // interval -- information //-------------------------------------------------------------------------- -template const int gRectangle::Dmnsn() const { return sides.Length(); } +template const int gRectangle::Dmnsn() const { return sides.size(); } template Gambit::Vector gRectangle::LowerBound() const { diff --git a/src/solvers/lcp/efglcp.cc b/src/solvers/lcp/efglcp.cc index d87657454..1dc22932b 100644 --- a/src/solvers/lcp/efglcp.cc +++ b/src/solvers/lcp/efglcp.cc @@ -73,7 +73,7 @@ bool NashLcpBehaviorSolver::Solution::AddBFS(const linalg::LemkeTableau &t } } - if (!m_list.Contains(cbfs)) { + if (!contains(m_list, cbfs)) { m_list.push_back(cbfs); return true; } diff --git a/src/solvers/lcp/nfglcp.cc b/src/solvers/lcp/nfglcp.cc index 012a4361e..04bfe5baa 100644 --- a/src/solvers/lcp/nfglcp.cc +++ b/src/solvers/lcp/nfglcp.cc @@ -117,7 +117,7 @@ template class NashLcpStrategySolver::Solution { List> m_bfsList; List> m_equilibria; - bool Contains(const Gambit::linalg::BFS &p_bfs) const { return m_bfsList.Contains(p_bfs); } + bool Contains(const Gambit::linalg::BFS &p_bfs) const { return contains(m_bfsList, p_bfs); } void push_back(const Gambit::linalg::BFS &p_bfs) { m_bfsList.push_back(p_bfs); } int EquilibriumCount() const { return m_equilibria.size(); } From 3ad9327fa098f15e480732e4d94d9caa3fc4f151 Mon Sep 17 00:00:00 2001 From: Ted Turocy Date: Thu, 12 Dec 2024 09:56:32 +0000 Subject: [PATCH 40/99] Move index_pair to be a type within gPolyList, use std::pair. --- src/solvers/enumpoly/gpolylst.h | 30 ++---------------------------- src/solvers/enumpoly/gpolylst.imp | 12 ++++++------ 2 files changed, 8 insertions(+), 34 deletions(-) diff --git a/src/solvers/enumpoly/gpolylst.h b/src/solvers/enumpoly/gpolylst.h index 7b4cb20ee..4e89ae5a5 100644 --- a/src/solvers/enumpoly/gpolylst.h +++ b/src/solvers/enumpoly/gpolylst.h @@ -27,35 +27,9 @@ #include "core/sqmatrix.h" #include "gpoly.h" -//! -//! Simple class for compact reference to pairs of indices -//! -class index_pair { -private: - const int m_first, m_second; - -public: - index_pair(int p_first, int p_second) : m_first(p_first), m_second(p_second) {} - - bool operator==(const index_pair &p_other) const - { - return (m_first == p_other.m_first && m_second == p_other.m_second); - } - bool operator!=(const index_pair &p_other) const - { - return (m_first != p_other.m_first || m_second != p_other.m_second); - } - int operator[](int p_index) const { return (p_index == 1) ? m_first : m_second; } -}; - -// *********************** -// class gPolyList -// *********************** +template class gPolyList { + using index_pair = std::pair; -template -class gPolyList -// : private Counted > -{ private: const gSpace *Space; const term_order *Order; diff --git a/src/solvers/enumpoly/gpolylst.imp b/src/solvers/enumpoly/gpolylst.imp index 30061de1c..1d7bbffed 100644 --- a/src/solvers/enumpoly/gpolylst.imp +++ b/src/solvers/enumpoly/gpolylst.imp @@ -324,16 +324,16 @@ template void gPolyList::Grobnerize(const term_order &order) while (!uncomputed.empty()) { int mindex = 1; for (ii = 2; ii <= uncomputed.size(); ii++) { - if (order.Less(List[uncomputed[ii][1]]->LeadingPowerProduct(order).LCM( - List[uncomputed[ii][2]]->LeadingPowerProduct(order)), - List[uncomputed[mindex][1]]->LeadingPowerProduct(order).LCM( - List[uncomputed[mindex][2]]->LeadingPowerProduct(order)))) { + if (order.Less(List[uncomputed[ii].first]->LeadingPowerProduct(order).LCM( + List[uncomputed[ii].second]->LeadingPowerProduct(order)), + List[uncomputed[mindex].first]->LeadingPowerProduct(order).LCM( + List[uncomputed[mindex].second]->LeadingPowerProduct(order)))) { mindex = ii; } } computed.push_back(uncomputed[mindex]); - int ii = uncomputed[mindex][1]; - int j = uncomputed[mindex][2]; + int ii = uncomputed[mindex].first; + int j = uncomputed[mindex].second; uncomputed.erase(std::next(uncomputed.begin(), mindex - 1)); if (!List[ii]->LeadingPowerProduct(order).UsesDifferentVariablesThan( List[j]->LeadingPowerProduct(order))) { From 9cfe468ffc1c3a52951c94460cbe188747915227 Mon Sep 17 00:00:00 2001 From: Ted Turocy Date: Thu, 12 Dec 2024 10:13:59 +0000 Subject: [PATCH 41/99] Remove unused polynomial-related code This removes some blocks of code which is no longer used. Much of this pertains to support for Grobner bases - which have not been used to attempt to solve polynomial systems for more than 20 years now. --- Makefile.am | 6 - src/solvers/enumpoly/gpolylst.h | 51 +--- src/solvers/enumpoly/gpolylst.imp | 408 ------------------------------ src/solvers/enumpoly/gsolver.cc | 26 -- src/solvers/enumpoly/gsolver.h | 54 ---- src/solvers/enumpoly/gsolver.imp | 216 ---------------- src/solvers/enumpoly/ideal.cc | 37 --- src/solvers/enumpoly/ideal.h | 86 ------- src/solvers/enumpoly/ideal.imp | 240 ------------------ src/solvers/enumpoly/ineqsolv.h | 19 +- src/solvers/enumpoly/odometer.cc | 141 ----------- src/solvers/enumpoly/odometer.h | 39 +-- src/solvers/enumpoly/prepoly.cc | 186 +------------- src/solvers/enumpoly/prepoly.h | 17 -- src/solvers/enumpoly/quiksolv.h | 22 +- src/solvers/enumpoly/quiksolv.imp | 199 +-------------- 16 files changed, 33 insertions(+), 1714 deletions(-) delete mode 100644 src/solvers/enumpoly/gsolver.cc delete mode 100644 src/solvers/enumpoly/gsolver.h delete mode 100644 src/solvers/enumpoly/gsolver.imp delete mode 100644 src/solvers/enumpoly/ideal.cc delete mode 100644 src/solvers/enumpoly/ideal.h delete mode 100644 src/solvers/enumpoly/ideal.imp diff --git a/Makefile.am b/Makefile.am index 70cac768d..24a0abeb5 100644 --- a/Makefile.am +++ b/Makefile.am @@ -413,12 +413,6 @@ gambit_enumpoly_SOURCES = \ 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 \ diff --git a/src/solvers/enumpoly/gpolylst.h b/src/solvers/enumpoly/gpolylst.h index 4e89ae5a5..6569c7f78 100644 --- a/src/solvers/enumpoly/gpolylst.h +++ b/src/solvers/enumpoly/gpolylst.h @@ -28,24 +28,14 @@ #include "gpoly.h" template class gPolyList { - using index_pair = std::pair; - private: const gSpace *Space; const term_order *Order; Gambit::List *> List; - // SubProcedures of ToSortedReducedGrobner - void Sort(const term_order &); - void CriterionTwo(Gambit::List &, const Gambit::List &, const int &, - const term_order &) const; - // See Adams and Loustaunau, p. 130 - void Grobnerize(const term_order &); - void GrobnerToMinimalGrobner(const term_order &); - void MinimalGrobnerToReducedGrobner(const term_order &); - public: - gPolyList(const gSpace *, const term_order *); + gPolyList(const gSpace *sp, const term_order *to) : Space(sp), Order(to) {} + gPolyList(const gSpace *, const term_order *, const Gambit::List *> &); gPolyList(const gSpace *, const term_order *, const Gambit::List> &); gPolyList(const gPolyList &); @@ -56,49 +46,28 @@ template class gPolyList { gPolyList &operator=(const gPolyList &); bool operator==(const gPolyList &) const; - bool operator!=(const gPolyList &) const; + bool operator!=(const gPolyList &x) const { return !(*this == x); } void operator+=(const gPoly &); void operator+=(const gPolyList &); - void operator+=(gPoly *); // NB - Doesn't copy pointee - // This can save a copy when one must create a - // polynomial, then do something in order to - // decide whether it should be added to the List - - gPoly operator[](const int) const; + // Takes ownership of pointer + void operator+=(gPoly *poly) { List.push_back(poly); } - // Residue of repeated reduction by members of the list - gPoly ReductionOf(const gPoly &, const term_order &) const; - bool SelfReduction(const int &, const term_order &); - - // Transform to canonical basis for associated ideal - gPolyList &ToSortedReducedGrobner(const term_order &); + const gPoly &operator[](const int index) const { return *(List[index]); } // New Coordinate Systems gPolyList TranslateOfSystem(const Gambit::Vector &) const; gPolyList SystemInNewCoordinates(const Gambit::SquareMatrix &) const; - // Truncations - gPolyList InteriorSegment(int, int) const; - // Information - const gSpace *AmbientSpace() const; - const term_order *TermOrder() const; - int Length() const; - int Dmnsn() const; + const gSpace *AmbientSpace() const { return Space; } + const term_order *TermOrder() const { return Order; } + int Length() const { return List.size(); } + int Dmnsn() const { return Space->Dmnsn(); } bool IsMultiaffine() const; - Gambit::List> UnderlyingList() const; Gambit::Vector Evaluate(const Gambit::Vector &) const; - bool IsRoot(const Gambit::Vector &) const; - Gambit::RectArray *> DerivativeMatrix() const; - gPoly DetOfDerivativeMatrix() const; - Gambit::Matrix DerivativeMatrix(const Gambit::Vector &) const; - Gambit::SquareMatrix SquareDerivativeMatrix(const Gambit::Vector &) const; - - // inline int static Count() { return Counted >::objCount(); } // Conversion - Gambit::List> ListTogDouble() const; Gambit::List> NormalizedList() const; }; diff --git a/src/solvers/enumpoly/gpolylst.imp b/src/solvers/enumpoly/gpolylst.imp index 1d7bbffed..06aef6527 100644 --- a/src/solvers/enumpoly/gpolylst.imp +++ b/src/solvers/enumpoly/gpolylst.imp @@ -42,10 +42,6 @@ Gambit::List InteriorSegment(const Gambit::List &p_list, int first, int la // Constructors / Destructors //--------------------------- -template -gPolyList::gPolyList(const gSpace *sp, const term_order *to) : Space(sp), Order(to), List() -{ -} template gPolyList::gPolyList(const gSpace *sp, const term_order *to, @@ -120,11 +116,6 @@ template bool gPolyList::operator==(const gPolyList &rhs) const return true; } -template bool gPolyList::operator!=(const gPolyList &rhs) const -{ - return !(*this == rhs); -} - template void gPolyList::operator+=(const gPoly &new_poly) { auto *temp = new gPoly(new_poly); @@ -138,288 +129,7 @@ template void gPolyList::operator+=(const gPolyList &new_list) } } -// NB - does not copy pointee - see gpolylst.h -template void gPolyList::operator+=(gPoly *new_poly_ptr) -{ - List.push_back(new_poly_ptr); -} - -template gPoly gPolyList::operator[](const int index) const -{ - return *(List[index]); -} - -//------------------------------------------------- -// Term Order and Grobner Basis Operations -//------------------------------------------------- - -template bool gPolyList::SelfReduction(const int &target, const term_order &order) -{ - // assert (!List[target]->IsZero()); - - gPoly tear_up(*List[target]); - gPoly reduction(Space, (T)0, &order); - bool target_was_reduced = false; - - while (!tear_up.IsZero()) { - int index = 1; - while (index <= List.size() && !tear_up.IsZero()) { - if (index == target || List[index]->IsZero()) { - index++; - } - else if (List[index]->LeadingPowerProduct(order) <= tear_up.LeadingPowerProduct(order)) { - tear_up.ReduceByDivisionAtExpV(order, *List[index], tear_up.LeadingPowerProduct(order)); - target_was_reduced = true; - index = 1; - } - if (!tear_up.IsZero()) { - reduction += tear_up.LeadingTerm(order); - tear_up -= tear_up.LeadingTerm(order); - } - } - } - *List[target] = reduction; - return target_was_reduced; -} - -template -gPoly gPolyList::ReductionOf(const gPoly &f, const term_order &order) const -{ - // assert (Space == f.GetSpace()); - - if (f.IsZero()) { - gPoly zero(Space, (T)0, f.GetOrder()); - return zero; - } - - gPoly tear_up(f); - gPoly reduction(Space, (T)0, f.GetOrder()); - - while (!tear_up.IsZero()) { - int index = 1; - while (index <= List.size() && !tear_up.IsZero()) { - if (List[index]->IsZero()) { - index++; - } - else if (List[index]->LeadingPowerProduct(order) <= tear_up.LeadingPowerProduct(order)) { - - tear_up.ReduceByDivisionAtExpV(order, *List[index], tear_up.LeadingPowerProduct(order)); - index = 1; - } - else { - index++; - } - } - if (!tear_up.IsZero()) { - reduction += tear_up.LeadingTerm(order); - tear_up -= tear_up.LeadingTerm(order); - } - } - return reduction; -} - -template void gPolyList::Sort(const term_order &order) -// bubble sort, justified since -// I expect List.Length() < 10 -{ - if (List.size() <= 1) { - return; - } - int ii; - for (ii = 1; ii < List.size(); ii++) { - if (!List[ii]->IsZero()) { - for (int j = ii + 1; j <= List.size(); j++) { - bool swap = false; - if (List[j]->IsZero()) { - swap = true; - } - else if (order.Less(List[j]->LeadingPowerProduct(order), - List[ii]->LeadingPowerProduct(order))) { - swap = true; - } - if (swap) { - gPoly *temp = List[ii]; - List[ii] = List[j]; - List[j] = temp; - } - } - } - } -} - -template -void gPolyList::CriterionTwo(Gambit::List &uncomputed, - const Gambit::List &computed, const int &no_polys, - const term_order &order) const -{ - for (int ell = 1; ell < no_polys; ell++) { - auto spot = std::find(uncomputed.begin(), uncomputed.end(), index_pair(ell, no_polys)); - if (spot != uncomputed.end()) { - for (int ii = 1; ii < no_polys; ii++) { - if (ii != ell && spot != uncomputed.end()) { - if (contains(uncomputed, index_pair(ii, ell)) || - contains(uncomputed, index_pair(ii, ell))) { - if (contains(uncomputed, index_pair(ii, no_polys))) { - exp_vect lpp_i = List[ii]->LeadingPowerProduct(order); - exp_vect lpp_ell = List[ell]->LeadingPowerProduct(order); - exp_vect lpp_no_polys = List[no_polys]->LeadingPowerProduct(order); - if (lpp_ell.Divides(lpp_i.LCM(lpp_no_polys))) { - uncomputed.erase(spot); - spot = uncomputed.end(); - } - } - } - } - } - } - } - for (int ii = 1; ii < no_polys; ii++) { - if (contains(uncomputed, index_pair(ii, no_polys))) { - for (int j = ii + 1; j < no_polys; j++) { - auto spot = std::find(uncomputed.begin(), uncomputed.end(), index_pair(ii, j)); - if (contains(uncomputed, index_pair(j, no_polys)) && spot != uncomputed.end()) { - exp_vect lpp_i = List[ii]->LeadingPowerProduct(order); - exp_vect lpp_j = List[j]->LeadingPowerProduct(order); - exp_vect lpp_no_polys = List[no_polys]->LeadingPowerProduct(order); - if (lpp_no_polys.Divides(lpp_i.LCM(lpp_j))) { - uncomputed.erase(spot); - } - } - } - } - } -} - -template void gPolyList::Grobnerize(const term_order &order) -{ - int index = 1; // Remove all 0's from List - while (index <= List.size()) { - if (List[index]->IsZero()) { - delete List[index]; - List.erase(std::next(List.begin(), index - 1)); - } - else { - index++; - } - } - - if (List.size() <= 1) { - return; - } - int ii; - for (ii = 1; ii <= List.size(); ii++) { - List[ii]->ToMonic(order); - } - - Gambit::List uncomputed; - Gambit::List computed; - - for (ii = 2; ii <= List.size(); ii++) { - for (int j = 1; j < ii; j++) { - uncomputed.push_back(index_pair(j, ii)); - } - CriterionTwo(uncomputed, computed, ii, order); - } - - while (!uncomputed.empty()) { - int mindex = 1; - for (ii = 2; ii <= uncomputed.size(); ii++) { - if (order.Less(List[uncomputed[ii].first]->LeadingPowerProduct(order).LCM( - List[uncomputed[ii].second]->LeadingPowerProduct(order)), - List[uncomputed[mindex].first]->LeadingPowerProduct(order).LCM( - List[uncomputed[mindex].second]->LeadingPowerProduct(order)))) { - mindex = ii; - } - } - computed.push_back(uncomputed[mindex]); - int ii = uncomputed[mindex].first; - int j = uncomputed[mindex].second; - uncomputed.erase(std::next(uncomputed.begin(), mindex - 1)); - if (!List[ii]->LeadingPowerProduct(order).UsesDifferentVariablesThan( - List[j]->LeadingPowerProduct(order))) { - - gPoly h = ReductionOf(List[ii]->S_Polynomial(order, *(List[j])), order); - if (!h.IsZero()) { - h.ToMonic(order); - auto *hptr = new gPoly(h); - List.push_back(hptr); - for (int k = 1; k < List.size(); k++) { - uncomputed.push_back(index_pair(k, List.size())); - } - CriterionTwo(uncomputed, computed, List.size(), order); - } - } - } -} - -template void gPolyList::GrobnerToMinimalGrobner(const term_order &order) -{ - if (Length() <= 1) { - return; - } - - int i = 1; - int j = 2; - while (j <= Length()) { - - if (List[i]->LeadingPowerProduct(order) <= List[j]->LeadingPowerProduct(order)) { - delete List[j]; - List.erase(std::next(List.begin(), j - 1)); - } - - else if (List[i]->LeadingPowerProduct(order) >= List[j]->LeadingPowerProduct(order)) { - delete List[i]; - List.erase(std::next(List.begin(), i - 1)); - if (i < j - 1) { - j--; - } - else { - i = 1; - } - } - - else { - if (i < j - 1) { - i++; - } - else { - i = 1; - j++; - } - } - } -} - -template void gPolyList::MinimalGrobnerToReducedGrobner(const term_order &order) -{ - if (Length() <= 1) { - return; - } - - int i = 1; - while (i <= List.size()) { - - gPolyList AllBut_ith(*this); - delete AllBut_ith.List[i]; - AllBut_ith.List.erase(std::next(AllBut_ith.List.begin(), i - 1)); - gPoly h = AllBut_ith.ReductionOf(*List[i], order); - delete List[i]; - List[i] = new gPoly(h); - - i++; - } -} - -template gPolyList &gPolyList::ToSortedReducedGrobner(const term_order &order) -{ - Grobnerize(order); - GrobnerToMinimalGrobner(order); - MinimalGrobnerToReducedGrobner(order); - Sort(order); - - return *this; -} //------------------------------------------ // New Coordinate Systems @@ -431,8 +141,6 @@ gPolyList gPolyList::TranslateOfSystem(const Gambit::Vector &new_origin Gambit::List> new_polys; for (int i = 1; i <= Length(); i++) { new_polys.push_back((*this)[i].TranslateOfPoly(new_origin)); - // assert (TranslateOfPoly((*this)[i],new_origin) == - // (*this)[i].TranslateOfPoly(new_origin)); } return gPolyList(AmbientSpace(), TermOrder(), new_polys); } @@ -442,35 +150,15 @@ gPolyList gPolyList::SystemInNewCoordinates(const Gambit::SquareMatrix { Gambit::List> new_polys; for (int i = 1; i <= Length(); i++) { - // assert ( (*this)[i].PolyInNewCoordinates(M) == - // gPoly( PolyInNewCoordinates((*this)[i],M) ) ); new_polys.push_back((*this)[i].PolyInNewCoordinates(M)); } return gPolyList(AmbientSpace(), TermOrder(), new_polys); } -//----------------------------------- -// Truncations -//----------------------------------- - -template gPolyList gPolyList::InteriorSegment(int first, int last) const -{ - return gPolyList(AmbientSpace(), TermOrder(), ::InteriorSegment(List, first, last)); -} - //---------------------------------- // Information //---------------------------------- -template Gambit::List> gPolyList::UnderlyingList() const -{ - Gambit::List> NewList; - for (const auto &v : List) { - NewList.push_back(*v); - } - return NewList; -} - template bool gPolyList::IsMultiaffine() const { for (const auto &v : List) { @@ -491,97 +179,6 @@ template Gambit::Vector gPolyList::Evaluate(const Gambit::Vector return answer; } -template bool gPolyList::IsRoot(const Gambit::Vector &v) const -{ - for (int ii = 1; ii <= List.size(); ii++) { - if (List[ii]->Evaluate(v) != (T)0) { - return false; - } - } - return true; -} - -template Gambit::RectArray *> gPolyList::DerivativeMatrix() const -{ - gPoly zero(Space, Order); - Gambit::RectArray *> answer(Length(), Dmnsn()); - int ii; - for (ii = 1; ii <= Length(); ii++) { - for (int j = 1; j <= Dmnsn(); j++) { - answer(ii, j) = new gPoly(UnderlyingList()[ii].PartialDerivative(j)); - } - } - - return answer; -} - -template gPoly gPolyList::DetOfDerivativeMatrix() const -{ - // assert(List.Length() == Space->Dmnsn()); - - int n = List.size(); - Gambit::RectArray *> deriv_matrix = DerivativeMatrix(); - gPoly answer(Space, Order); - - gPermutationOdometer odo(n); - - while (odo.Turn()) { - gPoly increment(Space, (T)1, Order); - for (int i = 1; i <= n; i++) { - increment *= *(deriv_matrix(i, odo[i])); - } - increment *= (T)odo.CurrentSign(); - answer += increment; - } - - return answer; -} - -template -Gambit::Matrix gPolyList::DerivativeMatrix(const Gambit::Vector &p) const -{ - Gambit::Matrix answer(Length(), Dmnsn()); - Gambit::List> list = UnderlyingList(); - int ii; - for (ii = 1; ii <= Length(); ii++) { - for (int j = 1; j <= Dmnsn(); j++) { - answer(ii, j) = list[ii].PartialDerivative(j).Evaluate(p); - } - } - - return answer; -} - -template -Gambit::SquareMatrix gPolyList::SquareDerivativeMatrix(const Gambit::Vector &p) const -{ - // assert (Length() == Dmnsn()); - - Gambit::SquareMatrix answer(Length()); - Gambit::List> list = UnderlyingList(); - int ii; - for (ii = 1; ii <= Length(); ii++) { - for (int j = 1; j <= Dmnsn(); j++) { - answer(ii, j) = list[ii].PartialDerivative(j).Evaluate(p); - } - } - - return answer; -} - -//---------------------------------- -// Conversion -//---------------------------------- - -template Gambit::List> gPolyList::ListTogDouble() const -{ - Gambit::List> newlist; - for (int ii = 1; ii <= List.size(); ii++) { - newlist.push_back(gPoly(TogDouble(*List[ii]))); - } - return newlist; -} - template Gambit::List> gPolyList::NormalizedList() const { Gambit::List> newlist; @@ -590,8 +187,3 @@ template Gambit::List> gPolyList::NormalizedList() co } return newlist; } - -template const gSpace *gPolyList::AmbientSpace() const { return Space; } -template const term_order *gPolyList::TermOrder() const { return Order; } -template int gPolyList::Length() const { return List.size(); } -template int gPolyList::Dmnsn() const { return Space->Dmnsn(); } diff --git a/src/solvers/enumpoly/gsolver.cc b/src/solvers/enumpoly/gsolver.cc deleted file mode 100644 index efcb0ba3a..000000000 --- a/src/solvers/enumpoly/gsolver.cc +++ /dev/null @@ -1,26 +0,0 @@ -// -// This file is part of Gambit -// Copyright (c) 1994-2024, The Gambit Project (http://www.gambit-project.org) -// -// FILE: src/tools/enumpoly/gsolver.cc -// Instantiation of gSolver classes -// -// This program is free software; you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation; either version 2 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -// - -#include "gsolver.imp" - -template class gSolver; -// template class gSolver; diff --git a/src/solvers/enumpoly/gsolver.h b/src/solvers/enumpoly/gsolver.h deleted file mode 100644 index f5abeba4f..000000000 --- a/src/solvers/enumpoly/gsolver.h +++ /dev/null @@ -1,54 +0,0 @@ -// -// This file is part of Gambit -// Copyright (c) 1994-2024, The Gambit Project (http://www.gambit-project.org) -// -// FILE: src/tools/enumpoly/gsolver.h -// Declaration of gSolver -// -// 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 GSOLVER_H -#define GSOLVER_H - -#include "ideal.h" -#include "linrcomb.h" -#include "gpolylst.h" - -template class gSolver { - -private: - const gPolyList &InputList; - const gIdeal TheIdeal; - - // Conversion - Gambit::List> BasisTogDouble() const; - - // Recursive Call in Solver - Gambit::List> - ContinuationSolutions(const Gambit::List> &list, int dmnsn, int curvar, - const Gambit::Vector &knownvals); - -public: - // Constructor and Destructor - gSolver(const term_order *Order, const gPolyList &Inputs); - gSolver(const gSolver &); - ~gSolver(); - - bool IsZeroDimensional(); - Gambit::List> Roots(); -}; - -#endif // GSOLVER_H diff --git a/src/solvers/enumpoly/gsolver.imp b/src/solvers/enumpoly/gsolver.imp deleted file mode 100644 index 22d9610c3..000000000 --- a/src/solvers/enumpoly/gsolver.imp +++ /dev/null @@ -1,216 +0,0 @@ -// -// This file is part of Gambit -// Copyright (c) 1994-2024, The Gambit Project (http://www.gambit-project.org) -// -// FILE: src/tools/enumpoly/gsolver.imp -// Implementation of class gSolver -// -// 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 "gsolver.h" - -//--------------------------------------------------------------- -// gSolver -//--------------------------------------------------------------- - -//--------------------------- -// Constructor / Destructor -//--------------------------- - -template -gSolver::gSolver(const term_order *Order, const gPolyList &Inputs) - : InputList(Inputs), TheIdeal(Order, Inputs) -{ -} - -template -gSolver::gSolver(const gSolver &given) : InputList(given.InputList), TheIdeal(given.TheIdeal) -{ -} - -template gSolver::~gSolver() = default; - -//--------------------------- -// Utilities -//--------------------------- - -template Gambit::List> gSolver::BasisTogDouble() const -{ - Gambit::List> newlist; - gPolyList oldlist = TheIdeal.CanonicalBasis(); - for (int i = 1; i <= oldlist.Length(); i++) { - - newlist.push_back(TogDouble(oldlist[i])); - } - return newlist; -} - -template -Gambit::List> -gSolver::ContinuationSolutions(const Gambit::List> &list, const int dmnsn, - const int curvar, const Gambit::Vector &knownvals) -{ - Gambit::List> answer; - - if (!list[1].IsZero()) { - polynomial rootpoly = list[1].UnivariateEquivalent(curvar); - - gInterval root_interval((double)-10, (double)10); // Ouch!! - auto error = (double)0.0001; // Ditto!! - - // DEBUG - // gout << "About to enter PreciseRoots ... \n"; - - Gambit::List rootlist = rootpoly.PreciseRoots(root_interval, error); - - // DEBUG - // gout << "Just left PreciseRoots ... \n"; - - for (int i = 1; i <= rootlist.size(); i++) { - Gambit::Vector newvec(knownvals); - newvec[curvar] = rootlist[i]; - if (curvar == dmnsn) { - answer.push_back(newvec); - } - else { - Gambit::List> newlist; - for (int j = 2; j <= list.size(); j++) { - newlist.push_back(list[j].EvaluateOneVar(curvar, rootlist[i])); - } - for (const auto &soln : ContinuationSolutions(newlist, dmnsn, curvar + 1, newvec)) { - answer.push_back(soln); - } - } - } - } - - else { // (list[1].IsZero()) - Gambit::List> newlist; - for (int j = 2; j <= list.size(); j++) { - newlist.push_back(list[j]); - } - for (const auto &soln : ContinuationSolutions(newlist, dmnsn, curvar, knownvals)) { - answer.push_back(soln); - } - } - - return answer; -} - -//--------------------------- -// Information -//--------------------------- - -template bool gSolver::IsZeroDimensional() { return TheIdeal.ZeroDimensional(); } - -/* - Old Implementation of Roots -template Gambit::List > gSolver::Roots() -{ - Gambit::List > dlist = BasisTogDouble(); - - int dmnsn = InputList.AmbientSpace()->Dmnsn(); - - Gambit::Vector origin(dmnsn); - for (int i = 1; i <= dmnsn; i++) origin[i] = (double)0; // Needless! - - Gambit::List > rootlist = ContinuationSolutions(dlist, - dmnsn, - 1, - origin); - return rootlist; -} -*/ - -template Gambit::List> gSolver::Roots() -{ - Gambit::List mon_bss = TheIdeal.MonomialBasis(); - - Gambit::List> UnivariateRootEquations; - int i; - for (i = 1; i <= TheIdeal.Dmnsn(); i++) { - Gambit::Matrix CoefficientMatrix(mon_bss.size() + 1, mon_bss.size()); - int j = 0; - bool done = false; - while (!done) { - exp_vect x_i_to_j(InputList.AmbientSpace(), i, j); - gPoly power(InputList.AmbientSpace(), x_i_to_j, (T)1, TheIdeal.Order()); - gPoly reduced_power = TheIdeal.CanonicalBasis().ReductionOf(power, *(TheIdeal.Order())); - for (int k = 1; k <= mon_bss.size(); k++) { - CoefficientMatrix(j + 1, k) = reduced_power.GetCoef(mon_bss[k]); - } - - // DEBUG - // gout << "After updating, j = " << j - // << "and CoefficientMatrix is\n" << CoefficientMatrix << "\n"; - - Gambit::Matrix SubMatrix(j + 1, mon_bss.size()); - for (int m = 1; m <= j + 1; m++) { - for (int n = 1; n <= mon_bss.size(); n++) { - SubMatrix(m, n) = CoefficientMatrix(m, n); - } - } - - // DEBUG - // gout << "Before entering Linear Combination, SubMatrix is\n" - // << SubMatrix << "\n"; - - LinearCombination Attempt(SubMatrix); - if (Attempt.LastRowIsSpanned()) { - polynomial root_eqn_i(Attempt.LinearDependence()); - UnivariateRootEquations.push_back(root_eqn_i); - done = true; - } - j++; - } - } - - Gambit::List> ConvertedUnivariateRootEquations; - for (const auto &eqn : UnivariateRootEquations) { - ConvertedUnivariateRootEquations.push_back(eqn.TogDouble()); - } - - Gambit::List> original; - Gambit::List> revised; - Gambit::Vector zero(TheIdeal.Dmnsn()); - original.push_back(zero); - - gInterval root_interval((double)-10, (double)10); // Ouch!! - auto error = (double)0.0001; // Ditto!! - - for (i = 1; i <= TheIdeal.Dmnsn(); i++) { - Gambit::List roots_of_eqn_i = - ConvertedUnivariateRootEquations[i].PreciseRoots(root_interval, error); - for (int j = 1; j <= original.size(); j++) { - for (int k = 1; k <= roots_of_eqn_i.size(); k++) { - Gambit::Vector new_vec = original[j]; - new_vec[i] = roots_of_eqn_i[k]; - revised.push_back(new_vec); - } - } - original = revised; - revised = Gambit::List>(); - } - - Gambit::List> finished; - Gambit::List> gDoublePolys = InputList.ListTogDouble(); - gPolyList InputListTogDouble(TheIdeal.TheSpace(), TheIdeal.Order(), gDoublePolys); - for (const auto &v : original) { - if (InputListTogDouble.IsRoot(v)) { - finished.push_back(v); - } - } - return finished; -} diff --git a/src/solvers/enumpoly/ideal.cc b/src/solvers/enumpoly/ideal.cc deleted file mode 100644 index ce98d86fa..000000000 --- a/src/solvers/enumpoly/ideal.cc +++ /dev/null @@ -1,37 +0,0 @@ -// -// This file is part of Gambit -// Copyright (c) 1994-2024, The Gambit Project (http://www.gambit-project.org) -// -// FILE: src/tools/enumpoly/ideal.cc -// Instantiations of gBasis 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 "ideal.imp" - -// template class gIdeal; -// template class gBasis; -// template gOutput &operator<<(gOutput &f, const gBasis &y); - -// template class gIdeal; -// template class gBasis; -// template gOutput &operator<<(gOutput &f, const gBasis &y); - -// template class gIdeal; -// template class gBasis; -// template gOutput &operator<<(gOutput &f, const gBasis &y); - -template class gIdeal; diff --git a/src/solvers/enumpoly/ideal.h b/src/solvers/enumpoly/ideal.h deleted file mode 100644 index 8c8bfa306..000000000 --- a/src/solvers/enumpoly/ideal.h +++ /dev/null @@ -1,86 +0,0 @@ -// -// This file is part of Gambit -// Copyright (c) 1994-2024, The Gambit Project (http://www.gambit-project.org) -// -// FILE: src/tools/enumpoly/ideal.h -// Declaration of gIdeal -// -// 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 IDEAL_H -#define IDEAL_H - -#include "odometer.h" -#include "gpolylst.h" - -/* - By the Hilbert basis theorem, every ideal is generated by some finite -basis, and for computational purposes we essentially think of ideals -as bases. There are some important operations, however, that differ -across the various bases generating a single ideal, and for this -reason we will have a separate basis class. Operator == is defined -setwise -- computationally we sort the two bases and then compare -termwise. - In class ideal operator == is defined in terms of the ideal generated. -Computationally two ideals agree if their ordered reduced Grobner bases are -the same. - */ - -// *********************** -// class gIdeal -// *********************** - -template class gIdeal { -private: - const gSpace *Space; - const term_order *order; - gPolyList basis; - -public: - gIdeal(const gSpace *, const term_order *); // Null gIdeal constructor - gIdeal(const gSpace *, const term_order *, const Gambit::List *> &); - gIdeal(const term_order *, const gPolyList &); - gIdeal(const gIdeal &); - - ~gIdeal(); - - // Operators - gIdeal &operator=(const gIdeal &); - - bool operator==(const gIdeal &) const; - bool operator!=(const gIdeal &) const; - gIdeal operator+(const gIdeal &) const; - gIdeal operator*(const gIdeal &) const; - - // Information - inline int Dmnsn() const { return Space->Dmnsn(); } - inline const gSpace *TheSpace() const { return Space; } - inline int NoBasisElements() const { return basis.Length(); } - inline const term_order *Order() const { return order; } - inline gPolyList CanonicalBasis() const { return basis; } - gIdeal MonomialIdeal() const; - Gambit::List MonomialBasis() const; - // This returns a monomial basis of the ring of polynomial - // functions on the variety V(I), where I is the given ideal. - // It fails if the variety is not zero dimensional. - bool IsRoot(const Gambit::Vector &) const; - - bool ZeroDimensional() const; - bool IsEntireRing() const; - bool Contains(gPoly &) const; -}; - -#endif // # IDEAL_H diff --git a/src/solvers/enumpoly/ideal.imp b/src/solvers/enumpoly/ideal.imp deleted file mode 100644 index 94e83cbb3..000000000 --- a/src/solvers/enumpoly/ideal.imp +++ /dev/null @@ -1,240 +0,0 @@ -// -// This file is part of Gambit -// Copyright (c) 1994-2024, The Gambit Project (http://www.gambit-project.org) -// -// FILE: src/tools/enumpoly/ideal.imp -// Implementation of gIdeal -// -// 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 "ideal.h" - -//--------------------------------------------------------------- -// gIdeal -//--------------------------------------------------------------- - -//--------------------------- -// Constructors / Destructors -//--------------------------- - -template -gIdeal::gIdeal(const gSpace *ls, const term_order *ordr) - : Space(ls), order(ordr), basis(ls, ordr) -{ -} - -template -gIdeal::gIdeal(const gSpace *ls, const term_order *ordr, const Gambit::List *> &polys) - : Space(ls), order(ordr), basis(ls, ordr, polys) -{ -} - -template -gIdeal::gIdeal(const term_order *ordr, const gPolyList &bss) - : Space(bss.AmbientSpace()), order(ordr), basis(bss) -{ - basis.ToSortedReducedGrobner(*ordr); -} - -template -gIdeal::gIdeal(const gIdeal &ideal) - : Space(ideal.Space), order(ideal.order), basis(ideal.basis) -{ -} - -template gIdeal::~gIdeal() = default; - -//---------------------------------- -// Operators -//---------------------------------- - -template gIdeal &gIdeal::operator=(const gIdeal &rhs) -{ - // assert (Space == rhs.Space); - - if (*this != rhs) { - this->order = rhs.order; - this->basis = rhs.basis; - } - return *this; -} - -template bool gIdeal::operator==(const gIdeal &rhs) const -{ - if (Space != rhs.Space) { - return false; - } - else { - if (*order == *(rhs.order)) { - if (basis == rhs.basis) { - return true; - } - else { - return false; - } - } - if (basis.Length() != rhs.basis.Length()) { - return false; - } - else { - // gPolyList reordered_rhs(rhs.basis,*order); - // gPolyList reordered_rhs(rhs.basis); - const gIdeal reordered_rhs(order, rhs.basis); - if (basis == reordered_rhs.basis) { - return true; - } - else { - return false; - } - } - } -} - -template bool gIdeal::operator!=(const gIdeal &rhs) const -{ - return !(*this == rhs); -} - -template gIdeal gIdeal::operator+(const gIdeal &rhs) const -{ - Gambit::List *> combined; - - for (int i = 1; i <= basis.Length(); i++) { - auto *temp = new gPoly(basis[i]); - combined.push_back(temp); - } - for (int j = 1; j <= rhs.basis.Length(); j++) { - auto *temp = new gPoly(rhs.basis[j]); - combined.push_back(temp); - } - - return gIdeal(Space, order, combined); -} - -template gIdeal gIdeal::operator*(const gIdeal &rhs) const -{ - Gambit::List *> basis_products; - - for (int i = 1; i <= basis.Length(); i++) { - for (int j = 1; j <= rhs.basis.Length(); j++) { - auto *temp = new gPoly(basis[i] * rhs.basis[j]); - basis_products.push_back(temp); - } - } - - return gIdeal(Space, order, basis_products); -} - -//---------------------------------- -// Information -//---------------------------------- - -template gIdeal gIdeal::MonomialIdeal() const -{ - gPolyList mon_bss(Space, order); - for (int i = 1; i <= basis.Length(); i++) { - mon_bss += basis[i].LeadingTerm(*order); - } - - return gIdeal(order, mon_bss); -} - -template Gambit::List gIdeal::MonomialBasis() const -{ - Gambit::List answer; - - Gambit::Array MinIndices(Dmnsn()), MaxIndices(Dmnsn()); - for (int i = 1; i <= Dmnsn(); i++) { - MinIndices[i] = 0; - } - - for (int j = 1; j <= basis.Length(); j++) { - if (basis[j].LeadingPowerProduct(*order).IsUnivariate()) { - MaxIndices[basis[j].LeadingPowerProduct(*order).SoleActiveVariable()] = - basis[j].LeadingPowerProduct(*order).TotalDegree() - 1; - } - } - - gIndexOdometer odometer(MinIndices, MaxIndices); - while (odometer.Turn()) { - exp_vect candidate(Space, odometer.CurrentIndices()); - bool add = true; - for (int j = 1; j <= basis.Length(); j++) { - if (basis[j].LeadingPowerProduct(*order) <= candidate) { - add = false; - } - } - if (add) { - answer.push_back(candidate); - } - } - - return answer; -} - -template bool gIdeal::IsRoot(const Gambit::Vector &v) const -{ - for (int i = 1; i <= basis.Length(); i++) { - if (basis[i].Evaluate(v) != (T)0) { - return false; - } - } - return true; -} - -template bool gIdeal::ZeroDimensional() const -{ - if (Dmnsn() == 0) { - return true; - } - - Gambit::Array HasLeadingTermThatIsPowerOfVariable(Dmnsn()); - int i; - for (i = 1; i <= Dmnsn(); i++) { - HasLeadingTermThatIsPowerOfVariable[i] = false; - } - - for (int j = 1; j <= basis.Length(); j++) { - - if (basis[j].LeadingPowerProduct(*order).IsConstant()) { - return false; - } - - if (basis[j].LeadingPowerProduct(*order).IsUnivariate()) { - HasLeadingTermThatIsPowerOfVariable - [basis[j].LeadingPowerProduct(*order).SoleActiveVariable()] = true; - } - } - - for (i = 1; i <= Dmnsn(); i++) { - if (!HasLeadingTermThatIsPowerOfVariable[i]) { - return false; - } - } - return true; -} - -template bool gIdeal::Contains(gPoly &f) const -{ - gPoly zero(Space, (T)0, order); - return (basis.ReductionOf(f, *order) == zero); -} - -template bool gIdeal::IsEntireRing() const -{ - gPoly one(Space, (T)1, order); - return Contains(one); -} diff --git a/src/solvers/enumpoly/ineqsolv.h b/src/solvers/enumpoly/ineqsolv.h index b35bd6393..5ce2f5bbc 100644 --- a/src/solvers/enumpoly/ineqsolv.h +++ b/src/solvers/enumpoly/ineqsolv.h @@ -25,7 +25,6 @@ #include "gambit.h" -#include "gsolver.h" #include "odometer.h" #include "rectangl.h" #include "gpoly.h" @@ -53,15 +52,11 @@ are required to be nonnegative. template class IneqSolv { private: - const gPolyList System; - const ListOfPartialTrees TreesOfPartials; + gPolyList System; + ListOfPartialTrees TreesOfPartials; T Epsilon; - // bool HasBeenSolved; - // gTriState HasASolution; - // Gambit::Vector Sample; // Routines Doing the Actual Work - bool IsASolution(const Gambit::Vector &) const; bool SystemHasNoSolutionIn(const gRectangle &r, Gambit::Array &) const; @@ -80,11 +75,11 @@ template class IneqSolv { bool operator!=(const IneqSolv &) const; // Information - inline const gSpace *AmbientSpace() const { return System.AmbientSpace(); } - inline const term_order *TermOrder() const { return System.TermOrder(); } - inline int Dmnsn() const { return System.Dmnsn(); } - inline gPolyList UnderlyingEquations() const { return System; } - inline T ErrorTolerance() const { return Epsilon; } + const gSpace *AmbientSpace() const { return System.AmbientSpace(); } + const term_order *TermOrder() const { return System.TermOrder(); } + int Dmnsn() const { return System.Dmnsn(); } + const gPolyList &UnderlyingEquations() const { return System; } + T ErrorTolerance() const { return Epsilon; } // The function that does everything bool ASolutionExists(const gRectangle &, Gambit::Vector &sample); diff --git a/src/solvers/enumpoly/odometer.cc b/src/solvers/enumpoly/odometer.cc index f37d79395..369d9b6b6 100644 --- a/src/solvers/enumpoly/odometer.cc +++ b/src/solvers/enumpoly/odometer.cc @@ -60,8 +60,6 @@ gIndexOdometer::gIndexOdometer(const Gambit::Array &IndexLowerBounds, // Manipulate //---------------------------------- -void gIndexOdometer::SetIndex(const int &place, const int &newind) { CurIndices[place] = newind; } - bool gIndexOdometer::Turn() { if (CurIndices[1] == MinIndices[1] - 1) { @@ -83,142 +81,3 @@ bool gIndexOdometer::Turn() CurIndices[turn_index]++; return true; } - -//---------------------------------- -// Information -//---------------------------------- - -int gIndexOdometer::NoIndices() const { return MaxIndices.Length(); } - -int gIndexOdometer::LinearIndex() const -{ - int index = (*this)[1]; - int factor = 1; - - for (int i = 2; i <= NoIndices(); i++) { - factor *= MaxIndices[i - 1] - MinIndices[i - 1] + 1; - index += factor * (CurIndices[i] - 1); - } - - return index; -} - -Gambit::Array gIndexOdometer::CurrentIndices() const { return CurIndices; } - -gIndexOdometer gIndexOdometer::AfterExcisionOf(int &to_be_zapped) const -{ - Gambit::Array NewMins, NewMaxs; - int i; - for (i = 1; i < to_be_zapped; i++) { - NewMins.push_back(MinIndices[i]); - NewMaxs.push_back(MaxIndices[i]); - } - for (i = to_be_zapped + 1; i <= NoIndices(); i++) { - NewMins.push_back(MinIndices[i]); - NewMaxs.push_back(MaxIndices[i]); - } - - gIndexOdometer NewOdo(NewMins, NewMaxs); - - for (i = 1; i < to_be_zapped; i++) { - NewOdo.SetIndex(i, CurIndices[i]); - } - for (i = to_be_zapped + 1; i <= NoIndices(); i++) { - NewOdo.SetIndex(i - 1, CurIndices[i]); - } - - return NewOdo; -} - -//--------------------------------------------------------------- -// gPermutationOdometer -//--------------------------------------------------------------- - -//--------------------------- -// Constructors / Destructors -//--------------------------- - -gPermutationOdometer::gPermutationOdometer(int given_n) : n(given_n), CurIndices(n), CurSign(0) -{ - CurIndices[1] = 0; // Codes for virginity - see Turn() below - for (int i = 2; i <= n; i++) { - CurIndices[i] = i; - } -} - -//---------------------------------- -// Operators -//---------------------------------- - -bool gPermutationOdometer::operator==(const gPermutationOdometer &rhs) const -{ - if (n != rhs.n) { - return false; - } - for (int i = 1; i <= n; i++) { - if (CurIndices[i] != rhs.CurIndices[i]) { - return false; - } - } - - if (CurSign != rhs.CurSign) { - // gout << "Error in gPermutationOdometer\n"; - exit(1); - } - - return true; -} - -bool gPermutationOdometer::operator!=(const gPermutationOdometer &rhs) const -{ - return !(*this == rhs); -} - -//---------------------------------- -// Manipulate -//---------------------------------- - -bool gPermutationOdometer::Turn() -{ - if (CurIndices[1] == 0) { // First turn gives identity permutation - CurIndices[1] = 1; - CurSign = 1; - return true; - } - - if (n == 1) { - return false; - } - - int cursor1 = n - 1; - while (cursor1 >= 1 && CurIndices[cursor1] > CurIndices[cursor1 + 1]) { - cursor1--; - } - - if (cursor1 == 0) { - return false; - } - - int cursor2 = cursor1 + 1; - while (cursor2 < n && CurIndices[cursor2 + 1] > CurIndices[cursor1]) { - cursor2++; - } - - int tmp = CurIndices[cursor2]; - CurIndices[cursor2] = CurIndices[cursor1]; - CurIndices[cursor1] = tmp; - CurSign *= -1; - - cursor1++; - cursor2 = n; - while (cursor1 < cursor2) { - tmp = CurIndices[cursor2]; - CurIndices[cursor2] = CurIndices[cursor1]; - CurIndices[cursor1] = tmp; - CurSign *= -1; - cursor1++; - cursor2--; - } - - return true; -} diff --git a/src/solvers/enumpoly/odometer.h b/src/solvers/enumpoly/odometer.h index e5f1ff468..277bc6d08 100644 --- a/src/solvers/enumpoly/odometer.h +++ b/src/solvers/enumpoly/odometer.h @@ -26,8 +26,6 @@ lexicographically, there is a relation between such a relation, and the numerical indexing derived from the lexicographical ordering, that is similar to an odometer. Here the least significant index is the first ("leftmost"). - - The second class provides a utility for cycling through the integers 1..n. */ #ifndef ODOMETER_H @@ -67,46 +65,11 @@ class gIndexOdometer { int operator[](int place) const { return CurIndices[place]; } - // Manipulate - void SetIndex(const int &, const int &); - bool Turn(); - - // Information - int NoIndices() const; - int LinearIndex() const; - Gambit::Array CurrentIndices() const; - gIndexOdometer AfterExcisionOf(int &) const; -}; - -// ***************************** -// class gPermutationOdometer -// ***************************** - -class gPermutationOdometer { -private: - int n; - Gambit::Array CurIndices; - int CurSign; - -public: - explicit gPermutationOdometer(int); - gPermutationOdometer(const gPermutationOdometer &) = default; - ~gPermutationOdometer() = default; - gPermutationOdometer &operator=(const gPermutationOdometer &) = delete; - - // Operators - bool operator==(const gPermutationOdometer &) const; - bool operator!=(const gPermutationOdometer &) const; - - int operator[](int place) const { return CurIndices[place]; } - // Manipulate bool Turn(); // Information - int NoIndices() const { return n; } - const Gambit::Array &CurrentIndices() const { return CurIndices; } - int CurrentSign() const { return CurSign; } + int NoIndices() const { return MaxIndices.Length(); } }; #endif // ODOMETER_H diff --git a/src/solvers/enumpoly/prepoly.cc b/src/solvers/enumpoly/prepoly.cc index d37c94cce..69b25e580 100644 --- a/src/solvers/enumpoly/prepoly.cc +++ b/src/solvers/enumpoly/prepoly.cc @@ -35,8 +35,6 @@ gSpace::gSpace(int nvars) : Variables() { Variable *newvar; - // assert (nvars >= 0); - for (int i = 1; i <= nvars; i++) { newvar = new Variable; newvar->Name = 'n'; @@ -70,8 +68,6 @@ gSpace::~gSpace() gSpace &gSpace::operator=(const gSpace &rhs) { - // gout<<"IF OK, ZAP ME:prepoly.cc7\n";//** - if (*this == rhs) { return *this; } @@ -84,45 +80,10 @@ int gSpace::Dmnsn() const { return Variables.Length(); } Variable *gSpace::VariableWithNumber(int i) const { return Variables[i]; } -const std::string &gSpace::GetVariableName(int i) const -{ - // gout<<"IF OK, ZAP ME:prepoly.cc10\n";//** - - return ((Variables[i])->Name); -} - -void gSpace::SetVariableName(int i, const std::string &s) { (Variables[i])->Name = s; } - -void gSpace::CreateVariables(int nvars) -{ - // gout<<"IF OK, ZAP ME:prepoly.cc12\n";//** - - Variable *var; - int n = Variables.Length(); - for (int i = 1; i <= nvars; i++) { - // gout<<"IF OK, ZAP ME:prepoly.cc13\n";//** - - var = new Variable; - var->Name = 'n'; - var->Name += Gambit::lexical_cast(n + i); - Variables.push_back(var); - } -} - -gSpace gSpace::WithVariableAppended() const -{ - // gout<<"IF OK, ZAP ME:prepoly.cc14\n";//** - gSpace enlarged(*this); - enlarged.CreateVariables(1); - return enlarged; -} - Variable *gSpace::operator[](int i) const { return VariableWithNumber(i); } bool gSpace::operator==(const gSpace &rhs) const { - // gout<<"IF OK, ZAP ME:prepoly.cc15\n";//** - if (Variables.Length() == rhs.Variables.Length() && Variables == rhs.Variables) { return true; } @@ -131,28 +92,7 @@ bool gSpace::operator==(const gSpace &rhs) const } } -bool gSpace::operator!=(const gSpace &rhs) const -{ - // gout<<"IF OK, ZAP ME:prepoly.cc16\n";//** - - return !(*this == rhs); -} - -// - RESTORE WHEN NEEDED -// gSpace gSpace::NewFamilyWithoutVariable(int var) -// {gout<<"IF OK, ZAP ME:prepoly.cc17\n";//** - -// gSpace result(NoOfVars - 1); -// for (int i = 1; i <= NoOfVars; i++) -// {gout<<"IF OK, ZAP ME:prepoly.cc18\n";//** - -// if (i < var) -// result.SetVariableName(i,GetVariableName(i)); -// else if (i > var) -// result.SetVariableName(i,GetVariableName(i+1)); -// } -// return result; -// } +bool gSpace::operator!=(const gSpace &rhs) const { return !(*this == rhs); } //------------------------------------------------------ // exp_vect @@ -220,8 +160,6 @@ int exp_vect::operator[](int index) const { return components[index]; } bool exp_vect::operator==(const exp_vect &RHS) const { - // assert (Space == RHS.Space); - if (components == RHS.components) { return true; } @@ -234,8 +172,6 @@ bool exp_vect::operator!=(const exp_vect &RHS) const { return !(*this == RHS); } bool exp_vect::operator<=(const exp_vect &RHS) const { - // assert (Space == RHS.Space); - for (int i = 1; i <= Dmnsn(); i++) { if (components[i] > RHS.components[i]) { return false; @@ -247,8 +183,6 @@ bool exp_vect::operator<=(const exp_vect &RHS) const bool exp_vect::operator>=(const exp_vect &RHS) const { - // assert (Space == RHS.Space); - for (int i = 1; i <= Dmnsn(); i++) { if (components[i] < RHS.components[i]) { return false; @@ -336,18 +270,6 @@ exp_vect exp_vect::LCM(const exp_vect &arg2) const return tmp; } -exp_vect exp_vect::WithVariableAppended(const gSpace *EnlargedSpace) const -{ - exp_vect tmp(EnlargedSpace); - - for (int i = 1; i <= Dmnsn(); i++) { - tmp.components[i] = components[i]; - } - tmp.components[Dmnsn() + 1] = 0; - - return tmp; -} - exp_vect exp_vect::AfterZeroingOutExpOfVariable(int &varnumber) const { exp_vect tmp(*this); @@ -368,32 +290,6 @@ exp_vect exp_vect::AfterDecrementingExpOfVariable(int &varnumber) const int exp_vect::Dmnsn() const { return Space->Dmnsn(); } -bool exp_vect::IsPositive() const -{ - // gout<<"IF OK, ZAP ME:prepoly.cc40\n";//** - - for (int i = 1; i <= Dmnsn(); i++) { - if (components[i] <= 0) { - return false; - } - } - - return true; -} - -bool exp_vect::IsNonnegative() const -{ - // gout<<"IF OK, ZAP ME:prepoly.cc41\n";//** - - for (int i = 1; i <= Dmnsn(); i++) { - if (components[i] < 0) { - return false; - } - } - - return true; -} - bool exp_vect::IsConstant() const { for (int i = 1; i <= Dmnsn(); i++) { @@ -414,39 +310,6 @@ bool exp_vect::IsMultiaffine() const return true; } -bool exp_vect::IsUnivariate() const -{ - int no_active_variables = 0; - - for (int i = 1; i <= Dmnsn(); i++) { - if ((*this)[i] > 0) { - no_active_variables++; - } - } - - if (no_active_variables == 1) { - return true; - } - else { - return false; - } -} - -int exp_vect::SoleActiveVariable() const -{ - int sole_active_variable = 0; - - for (int i = 1; i <= Dmnsn(); i++) { - if ((*this)[i] > 0) { - // assert(sole_active_variable == 0); - sole_active_variable = i; - } - } - - // assert (sole_active_variable > 0); - return sole_active_variable; -} - int exp_vect::TotalDegree() const { int exp_sum = 0; @@ -456,37 +319,10 @@ int exp_vect::TotalDegree() const return exp_sum; } -bool exp_vect::Divides(const exp_vect &n) const -{ - for (int i = 1; i <= Dmnsn(); i++) { - if ((*this)[i] > n[i]) { - return false; - } - } - return true; -} - -bool exp_vect::UsesDifferentVariablesThan(const exp_vect &n) const -{ - for (int i = 1; i <= Dmnsn(); i++) { - if (((*this)[i] > 0) && (n[i] > 0)) { - return false; - } - } - return true; -} - //-------------------------- // Manipulation //-------------------------- -void exp_vect::SetExp(int varno, int pow) -{ - // assert (1 <= varno && varno <= Dmnsn() && 0 <= pow); - - components[varno] = pow; -} - void exp_vect::ToZero() { for (int i = 1; i <= Dmnsn(); i++) { @@ -620,8 +456,6 @@ term_order::term_order(const gSpace *p, ORD_PTR act_ord) : Space(p), actual_orde term_order &term_order::operator=(const term_order &RHS) { - // gout<<"IF OK, ZAP ME:prepoly.cc50\n";//** - if (this == &RHS) { return *this; } @@ -636,12 +470,7 @@ bool term_order::operator==(const term_order &RHS) const return (Space == RHS.Space && actual_order == RHS.actual_order); } -bool term_order::operator!=(const term_order &RHS) const -{ - // gout<<"IF OK, ZAP ME:prepoly.cc52\n";//** - - return !(*this == RHS); -} +bool term_order::operator!=(const term_order &RHS) const { return !(*this == RHS); } //------------------------- // Comparisons @@ -664,16 +493,5 @@ bool term_order::Greater(const exp_vect &LHS, const exp_vect &RHS) const bool term_order::GreaterOrEqual(const exp_vect &LHS, const exp_vect &RHS) const { - // gout<<"IF OK, ZAP ME:prepoly.cc56\n";//** - return !(Less(LHS, RHS)); } - -//------------------------------------------- -// Manipulation and Information -//------------------------------------------- - -term_order term_order::WithVariableAppended(const gSpace *ExtendedSpace) const -{ - return {ExtendedSpace, actual_order}; -} diff --git a/src/solvers/enumpoly/prepoly.h b/src/solvers/enumpoly/prepoly.h index 9fcb33842..18be0074a 100644 --- a/src/solvers/enumpoly/prepoly.h +++ b/src/solvers/enumpoly/prepoly.h @@ -69,12 +69,6 @@ class gSpace { // information int Dmnsn() const; Variable *VariableWithNumber(int) const; - const std::string &GetVariableName(int) const; - gSpace WithVariableAppended() const; - - // manipulation - void SetVariableName(int, const std::string &); - void CreateVariables(int nvars = 1); }; // *********************** @@ -122,24 +116,16 @@ class exp_vect { // Other operations exp_vect LCM(const exp_vect &) const; - exp_vect WithVariableAppended(const gSpace *) const; exp_vect AfterZeroingOutExpOfVariable(int &) const; exp_vect AfterDecrementingExpOfVariable(int &) const; // Information int Dmnsn() const; - bool IsPositive() const; - bool IsNonnegative() const; bool IsConstant() const; bool IsMultiaffine() const; - bool IsUnivariate() const; - int SoleActiveVariable() const; int TotalDegree() const; - bool Divides(const exp_vect &) const; - bool UsesDifferentVariablesThan(const exp_vect &) const; // Manipulation - void SetExp(int varno, int pow); void ToZero(); }; @@ -191,9 +177,6 @@ class term_order { bool LessOrEqual(const exp_vect &, const exp_vect &) const; bool Greater(const exp_vect &, const exp_vect &) const; bool GreaterOrEqual(const exp_vect &, const exp_vect &) const; - - // Manipulation and Information - term_order WithVariableAppended(const gSpace *) const; }; #endif // PREPOLY_H diff --git a/src/solvers/enumpoly/quiksolv.h b/src/solvers/enumpoly/quiksolv.h index af2d64e7b..292f00340 100644 --- a/src/solvers/enumpoly/quiksolv.h +++ b/src/solvers/enumpoly/quiksolv.h @@ -25,7 +25,6 @@ #include "gambit.h" #include "odometer.h" -#include "gsolver.h" #include "rectangl.h" #include "gpoly.h" #include "gpolylst.h" @@ -91,7 +90,6 @@ template class QuikSolv { // Ask whether Newton's method leads to a root bool NewtonRootInRectangle(const gRectangle &, Gambit::Vector &) const; - bool NewtonRootNearRectangle(const gRectangle &, Gambit::Vector &) const; // Ask whether we can prove that there is no root other than // the one produced by the last step @@ -114,22 +112,10 @@ template class QuikSolv { const int &, Gambit::Array &, int &iterations, int depth, const int &, int *) const; - bool ARootExistsRecursion(const gRectangle &, Gambit::Vector &, - const gRectangle &, Gambit::Array &) const; - public: - class NewtonError : public Gambit::Exception { - public: - ~NewtonError() noexcept override = default; - const char *what() const noexcept override - { - return "Newton method failed to polish approximate root"; - } - }; explicit QuikSolv(const gPolyList &); - QuikSolv(const gPolyList &, const int &); QuikSolv(const QuikSolv &); - ~QuikSolv(); + ~QuikSolv() = default; // Operators QuikSolv &operator=(const QuikSolv &); @@ -148,15 +134,9 @@ template class QuikSolv { // Refines the accuracy of roots obtained from other algorithms Gambit::Vector NewtonPolishOnce(const Gambit::Vector &) const; Gambit::Vector SlowNewtonPolishOnce(const Gambit::Vector &) const; - Gambit::Vector NewtonPolishedRoot(const Gambit::Vector &) const; - - // Checks for complex singular roots - bool MightHaveSingularRoots() const; // The grand calculation - returns true if successful bool FindCertainNumberOfRoots(const gRectangle &, const int &, const int &); - bool FindRoots(const gRectangle &, const int &); - bool ARootExists(const gRectangle &, Gambit::Vector &) const; }; #endif // QUIKSOLV_H diff --git a/src/solvers/enumpoly/quiksolv.imp b/src/solvers/enumpoly/quiksolv.imp index e409a39dd..8154757d2 100644 --- a/src/solvers/enumpoly/quiksolv.imp +++ b/src/solvers/enumpoly/quiksolv.imp @@ -42,15 +42,6 @@ QuikSolv::QuikSolv(const gPolyList &given) { } -template -QuikSolv::QuikSolv(const gPolyList &given, const int &no_eqs) - : System(given), gDoubleSystem(given.AmbientSpace(), given.TermOrder(), given.NormalizedList()), - NoEquations(no_eqs), NoInequalities(System.Length() - no_eqs), TreesOfPartials(gDoubleSystem), - HasBeenSolved(false), Roots(), isMultiaffine(System.IsMultiaffine()), - Equation_i_uses_var_j(Eq_i_Uses_j()) -{ -} - template QuikSolv::QuikSolv(const QuikSolv &qs) : System(qs.System), gDoubleSystem(qs.gDoubleSystem), NoEquations(qs.NoEquations), @@ -61,8 +52,6 @@ QuikSolv::QuikSolv(const QuikSolv &qs) { } -template QuikSolv::~QuikSolv() = default; - //------------------------------------------------------- // Supporting Calculations for the Constructors //------------------------------------------------------- @@ -237,37 +226,6 @@ bool QuikSolv::NewtonRootInRectangle(const gRectangle &r, return false; } -template -bool QuikSolv::NewtonRootNearRectangle(const gRectangle &r, - Gambit::Vector &point) const -{ - Gambit::Vector zero(NoEquations); - for (int i = 1; i <= NoEquations; i++) { - zero[i] = (double)0; - } - - Gambit::Vector oldevals = TreesOfPartials.ValuesOfRootPolys(point, NoEquations); - - gRectangle bigr = r.CubeContainingCrcmscrbngSphere(); - - if (fuzzy_equals(oldevals, zero)) { - return bigr.Contains(point); - } - - while (true) { - Gambit::Vector newpoint = SlowNewtonPolishOnce(point); - if (!bigr.Contains(newpoint)) { - return false; - } - point = newpoint; - Gambit::Vector newevals = TreesOfPartials.ValuesOfRootPolys(point, NoEquations); - if (fuzzy_equals(newevals, zero)) { - return r.Contains(point); - } - - oldevals = newevals; - } -} //------------------------------------ // Is the Newton root the only root? @@ -430,66 +388,15 @@ Gambit::Vector QuikSolv::SlowNewtonPolishOnce(const Gambit::Vector -Gambit::Vector QuikSolv::NewtonPolishedRoot(const Gambit::Vector &initial) const -{ - Gambit::List> interval_list; - for (int i = 1; i <= Dmnsn(); i++) { - interval_list.push_back(gInterval(initial[i] - (double)1, initial[i] + (double)1)); - } - gRectangle box(interval_list); - Gambit::Vector point(initial); - if (!NewtonRootInRectangle(box, point)) { - throw NewtonError(); - } - point = SlowNewtonPolishOnce(point); - point = SlowNewtonPolishOnce(point); - return point; -} - -//------------------------------------------- -// Check for Singularity -//------------------------------------------- - -template bool QuikSolv::MightHaveSingularRoots() const -{ - // assert (NoEquations == System.Dmnsn()); - - gPoly newpoly = gDoubleSystem.ReductionOf(gDoubleSystem.DetOfDerivativeMatrix(), - *(gDoubleSystem.TermOrder())); - - if (newpoly.IsZero()) { - return true; - } - - Gambit::List> newlist(gDoubleSystem.UnderlyingList()); - - newlist.push_back(newpoly); - gPolyList larger_system(gDoubleSystem.AmbientSpace(), gDoubleSystem.TermOrder(), - newlist); - gIdeal test_ideal(gDoubleSystem.TermOrder(), larger_system); - - return !(test_ideal.IsEntireRing()); -} //------------------------------------------- // The Central Calculation //------------------------------------------- -template bool QuikSolv::FindRoots(const gRectangle &r, const int &max_iterations) -{ - // assert (NoEquations == System.Dmnsn()); - - int zero = 0; - return FindCertainNumberOfRoots(r, max_iterations, zero); -} - template bool QuikSolv::FindCertainNumberOfRoots(const gRectangle &r, const int &max_iterations, const int &max_no_roots) { - // assert (NoEquations == System.Dmnsn()); - auto *rootlistptr = new Gambit::List>(); if (NoEquations == 0) { @@ -519,47 +426,26 @@ bool QuikSolv::FindCertainNumberOfRoots(const gRectangle &r, const int &ma } return false; - - /* - This is code that was once used to call the Grobner basis solver - It will not work with T = gDouble or double, due to numerical instability - gSolver bigsolver(System.TermOrder(), System); - - if ( !bigsolver.IsZeroDimensional() ) return false; - else { - Gambit::List > rootlist; - rootlist = bigsolver.Roots(); - Gambit::List > roots; - for (int j = 1; j <= rootlist.Length(); j++) - if (TogDouble(r).Contains(rootlist[j])) roots += rootlist[j]; - Roots = roots; - } - return true; - */ } -// -// TLT: In some cases, this recursive process apparently goes into an -// infinite regress. I'm not able to identify just why this occurs, -// but as at least a temporary safeguard, we will limit the maximum depth -// of this recursive search. -// -// This limit has been chosen only because it doesn't look like any -// "serious" search (i.e., one that actually terminates with a result) -// will take more than a depth of 32. -// -#define MAX_DEPTH 32 -template -void // Gambit::List > +template void QuikSolv::FindRootsRecursion(Gambit::List> *rootlistptr, const gRectangle &r, const int &max_iterations, Gambit::Array &precedence, int &iterations, int depth, const int &max_no_roots, int *roots_found) const { - // assert (NoEquations == System.Dmnsn()); - - // Check for user interrupt - // m_status.SetProgress(50.0); + // + // TLT: In some cases, this recursive process apparently goes into an + // infinite regress. I'm not able to identify just why this occurs, + // but as at least a temporary safeguard, we will limit the maximum depth + // of this recursive search. + // + // This limit has been chosen only because it doesn't look like any + // "serious" search (i.e., one that actually terminates with a result) + // will take more than a depth of 32. + // + const int MAX_DEPTH = 32; if (SystemHasNoRootsIn(r, precedence)) { return; @@ -602,64 +488,3 @@ QuikSolv::FindRootsRecursion(Gambit::List> *rootlistpt } } } - -template -bool QuikSolv::ARootExistsRecursion(const gRectangle &r, Gambit::Vector &sample, - const gRectangle &smallrect, - Gambit::Array &precedence) const -{ - if (fuzzy_equals(smallrect.MaximalSideLength(), (double)0.0)) { - sample = smallrect.Center(); - return true; - } - - if (SystemHasNoRootsIn(smallrect, precedence)) { - return false; - } - - Gambit::Vector point = smallrect.Center(); - if (NewtonRootNearRectangle(smallrect, point)) { - if (r.Contains(point)) { - bool satisfies_inequalities(true); - for (int i = NoEquations + 1; i <= System.Length(); i++) { - if (satisfies_inequalities) { - if (TreesOfPartials[i].ValueOfRootPoly(point) < (double)0) { - satisfies_inequalities = false; - } - } - } - if (satisfies_inequalities) { - sample = point; - return true; - } - } - } - - int N = smallrect.NumberOfCellsInSubdivision(); - for (int i = 1; i <= N; i++) { - if (ARootExistsRecursion(r, sample, smallrect.SubdivisionCell(i), precedence)) { - return true; - } - } - - return false; -} - -template -bool QuikSolv::ARootExists(const gRectangle &r, Gambit::Vector &sample) const -{ - if (NoEquations == 0) { - Gambit::Vector answer(0); - sample = answer; - return true; - } - - gRectangle r_double(TogDouble(r)); - Gambit::Array precedence(System.Length()); - // Orders search for nonvanishing poly - for (int i = 1; i <= System.Length(); i++) { - precedence[i] = i; - } - - return ARootExistsRecursion(r_double, sample, r_double, precedence); -} From f2cf68b07a5daee136cc45a6adc86eaebb691339 Mon Sep 17 00:00:00 2001 From: Ted Turocy Date: Thu, 12 Dec 2024 11:57:41 +0000 Subject: [PATCH 42/99] Refactor polynomial-related classes formerly in prepoly.h This reorganises and simplifies some of the preliminary classes underlying the implementation of multivariate polynomials: * Ordering of exponent vectors is now a property of vectors (or more properly the space) and not a separate concept * Functions inlined - most of the operations in these classes are simple and suitable for inlining * Rationalised and standardised namings of the concepts * Moved to gpoly.h so the polynomial implementation is now self-contained. --- Makefile.am | 8 - src/solvers/enumpoly/behavextend.cc | 87 ++- src/solvers/enumpoly/efgpoly.cc | 18 +- src/solvers/enumpoly/gpoly.cc | 20 +- src/solvers/enumpoly/gpoly.h | 289 +++++++--- src/solvers/enumpoly/gpoly.imp | 560 ++---------------- src/solvers/enumpoly/gpolylst.h | 12 +- src/solvers/enumpoly/gpolylst.imp | 19 +- src/solvers/enumpoly/ineqsolv.h | 3 +- src/solvers/enumpoly/monomial.cc | 27 - src/solvers/enumpoly/monomial.h | 66 --- src/solvers/enumpoly/monomial.imp | 134 ----- src/solvers/enumpoly/nfgpoly.cc | 26 +- src/solvers/enumpoly/poly.cc | 26 - src/solvers/enumpoly/poly.h | 150 ----- src/solvers/enumpoly/poly.imp | 862 ---------------------------- src/solvers/enumpoly/prepoly.cc | 497 ---------------- src/solvers/enumpoly/prepoly.h | 182 ------ src/solvers/enumpoly/quiksolv.h | 3 +- src/solvers/enumpoly/quiksolv.imp | 15 +- 20 files changed, 327 insertions(+), 2677 deletions(-) delete mode 100644 src/solvers/enumpoly/monomial.cc delete mode 100644 src/solvers/enumpoly/monomial.h delete mode 100644 src/solvers/enumpoly/monomial.imp delete mode 100644 src/solvers/enumpoly/poly.cc delete mode 100644 src/solvers/enumpoly/poly.h delete mode 100644 src/solvers/enumpoly/poly.imp delete mode 100644 src/solvers/enumpoly/prepoly.cc delete mode 100644 src/solvers/enumpoly/prepoly.h diff --git a/Makefile.am b/Makefile.am index 24a0abeb5..51504aac6 100644 --- a/Makefile.am +++ b/Makefile.am @@ -417,14 +417,6 @@ gambit_enumpoly_SOURCES = \ 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 \ - 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 \ diff --git a/src/solvers/enumpoly/behavextend.cc b/src/solvers/enumpoly/behavextend.cc index 83d08d8a7..03fa78b1e 100644 --- a/src/solvers/enumpoly/behavextend.cc +++ b/src/solvers/enumpoly/behavextend.cc @@ -85,19 +85,19 @@ List DeviationInfosets(const BehaviorSupportProfile &big_supp, cons } gPolyList ActionProbsSumToOneIneqs(const MixedBehaviorProfile &p_solution, - const gSpace &BehavStratSpace, const term_order &Lex, + const VariableSpace &BehavStratSpace, const BehaviorSupportProfile &big_supp, const std::map &var_index) { - gPolyList answer(&BehavStratSpace, &Lex); + gPolyList answer(&BehavStratSpace); for (auto player : p_solution.GetGame()->GetPlayers()) { for (auto infoset : player->GetInfosets()) { if (!big_supp.HasAction(infoset)) { int index_base = var_index.at(infoset); - gPoly factor(&BehavStratSpace, 1.0, &Lex); + gPoly factor(&BehavStratSpace, 1.0); for (int k = 1; k < infoset->NumActions(); k++) { - factor -= gPoly(&BehavStratSpace, index_base + k, 1, &Lex); + factor -= gPoly(&BehavStratSpace, index_base + k, 1); } answer += factor; } @@ -177,8 +177,8 @@ std::list DeviationSupports(const BehaviorSupportProfile } bool NashNodeProbabilityPoly(const MixedBehaviorProfile &p_solution, - gPoly &node_prob, const gSpace &BehavStratSpace, - const term_order &Lex, const BehaviorSupportProfile &dsupp, + gPoly &node_prob, const VariableSpace &BehavStratSpace, + const BehaviorSupportProfile &dsupp, const std::map &var_index, GameNode tempnode, const GameInfoset &iset, const GameAction &act) { @@ -209,12 +209,12 @@ bool NashNodeProbabilityPoly(const MixedBehaviorProfile &p_solution, int initial_var_no = var_index.at(last_infoset); if (last_action->GetNumber() < last_infoset->NumActions()) { int varno = initial_var_no + last_action->GetNumber(); - node_prob *= gPoly(&BehavStratSpace, varno, 1, &Lex); + node_prob *= gPoly(&BehavStratSpace, varno, 1); } else { - gPoly factor(&BehavStratSpace, 1.0, &Lex); + gPoly factor(&BehavStratSpace, 1.0); for (int k = 1; k < last_infoset->NumActions(); k++) { - factor -= gPoly(&BehavStratSpace, initial_var_no + k, 1, &Lex); + factor -= gPoly(&BehavStratSpace, initial_var_no + k, 1); } node_prob *= factor; } @@ -225,12 +225,12 @@ bool NashNodeProbabilityPoly(const MixedBehaviorProfile &p_solution, } gPolyList NashExpectedPayoffDiffPolys(const MixedBehaviorProfile &p_solution, - const gSpace &BehavStratSpace, const term_order &Lex, + const VariableSpace &BehavStratSpace, const BehaviorSupportProfile &little_supp, const BehaviorSupportProfile &big_supp, const std::map &var_index) { - gPolyList answer(&BehavStratSpace, &Lex); + gPolyList answer(&BehavStratSpace); auto terminal_nodes = TerminalNodes(p_solution.GetGame()); @@ -249,12 +249,12 @@ gPolyList NashExpectedPayoffDiffPolys(const MixedBehaviorProfile // The utility difference between the // payoff resulting from the profile and deviation to // the strategy for pl specified by dsupp[k] - gPoly next_poly(&BehavStratSpace, &Lex); + gPoly next_poly(&BehavStratSpace); for (auto node : terminal_nodes) { - gPoly node_prob(&BehavStratSpace, 1.0, &Lex); - if (NashNodeProbabilityPoly(p_solution, node_prob, BehavStratSpace, Lex, support, - var_index, node, infoset, action)) { + gPoly node_prob(&BehavStratSpace, 1.0); + if (NashNodeProbabilityPoly(p_solution, node_prob, BehavStratSpace, support, var_index, + node, infoset, action)) { if (node->GetOutcome()) { node_prob *= static_cast(node->GetOutcome()->GetPayoff(player)); } @@ -270,15 +270,15 @@ gPolyList NashExpectedPayoffDiffPolys(const MixedBehaviorProfile } gPolyList ExtendsToNashIneqs(const MixedBehaviorProfile &p_solution, - const gSpace &BehavStratSpace, const term_order &Lex, + const VariableSpace &BehavStratSpace, const BehaviorSupportProfile &little_supp, const BehaviorSupportProfile &big_supp, const std::map &var_index) { - gPolyList answer(&BehavStratSpace, &Lex); - answer += ActionProbsSumToOneIneqs(p_solution, BehavStratSpace, Lex, big_supp, var_index); - answer += NashExpectedPayoffDiffPolys(p_solution, BehavStratSpace, Lex, little_supp, big_supp, - var_index); + gPolyList answer(&BehavStratSpace); + answer += ActionProbsSumToOneIneqs(p_solution, BehavStratSpace, big_supp, var_index); + answer += + NashExpectedPayoffDiffPolys(p_solution, BehavStratSpace, little_supp, big_supp, var_index); return answer; } @@ -306,12 +306,10 @@ bool ExtendsToNash(const MixedBehaviorProfile &p_solution, } // We establish the space - gSpace BehavStratSpace(num_vars); - ORD_PTR ptr = &lex; - term_order Lex(&BehavStratSpace, ptr); + VariableSpace BehavStratSpace(num_vars); gPolyList inequalities = - ExtendsToNashIneqs(p_solution, BehavStratSpace, Lex, little_supp, big_supp, var_index); + ExtendsToNashIneqs(p_solution, BehavStratSpace, little_supp, big_supp, var_index); // set up the rectangle of search Vector bottoms(num_vars), tops(num_vars); bottoms = 0; @@ -328,8 +326,8 @@ bool ExtendsToNash(const MixedBehaviorProfile &p_solution, namespace { bool ANFNodeProbabilityPoly(const MixedBehaviorProfile &p_solution, - gPoly &node_prob, const gSpace &BehavStratSpace, - const term_order &Lex, const BehaviorSupportProfile &big_supp, + gPoly &node_prob, const VariableSpace &BehavStratSpace, + const BehaviorSupportProfile &big_supp, const std::map &var_index, GameNode tempnode, int pl, int i, int j) { @@ -357,12 +355,12 @@ bool ANFNodeProbabilityPoly(const MixedBehaviorProfile &p_solution, int initial_var_no = var_index.at(last_infoset); if (last_action->GetNumber() < last_infoset->NumActions()) { int varno = initial_var_no + last_action->GetNumber(); - node_prob *= gPoly(&BehavStratSpace, varno, 1, &Lex); + node_prob *= gPoly(&BehavStratSpace, varno, 1); } else { - gPoly factor(&BehavStratSpace, 1.0, &Lex); + gPoly factor(&BehavStratSpace, 1.0); for (int k = 1; k < last_infoset->NumActions(); k++) { - factor -= gPoly(&BehavStratSpace, initial_var_no + k, 1, &Lex); + factor -= gPoly(&BehavStratSpace, initial_var_no + k, 1); } node_prob *= factor; } @@ -373,12 +371,12 @@ bool ANFNodeProbabilityPoly(const MixedBehaviorProfile &p_solution, } gPolyList ANFExpectedPayoffDiffPolys(const MixedBehaviorProfile &p_solution, - const gSpace &BehavStratSpace, const term_order &Lex, + const VariableSpace &BehavStratSpace, const BehaviorSupportProfile &little_supp, const BehaviorSupportProfile &big_supp, const std::map &var_index) { - gPolyList answer(&BehavStratSpace, &Lex); + gPolyList answer(&BehavStratSpace); auto terminal_nodes = TerminalNodes(p_solution.GetGame()); @@ -394,12 +392,12 @@ gPolyList ANFExpectedPayoffDiffPolys(const MixedBehaviorProfile // This will be the utility difference between the // payoff resulting from the profile and deviation to // action j - gPoly next_poly(&BehavStratSpace, &Lex); + gPoly next_poly(&BehavStratSpace); for (auto terminal : terminal_nodes) { - gPoly node_prob(&BehavStratSpace, 1.0, &Lex); - if (ANFNodeProbabilityPoly(p_solution, node_prob, BehavStratSpace, Lex, big_supp, - var_index, terminal, player->GetNumber(), - infoset->GetNumber(), action->GetNumber())) { + gPoly node_prob(&BehavStratSpace, 1.0); + if (ANFNodeProbabilityPoly(p_solution, node_prob, BehavStratSpace, big_supp, var_index, + terminal, player->GetNumber(), infoset->GetNumber(), + action->GetNumber())) { if (terminal->GetOutcome()) { node_prob *= static_cast(terminal->GetOutcome()->GetPayoff(player)); } @@ -414,15 +412,15 @@ gPolyList ANFExpectedPayoffDiffPolys(const MixedBehaviorProfile } gPolyList ExtendsToANFNashIneqs(const MixedBehaviorProfile &p_solution, - const gSpace &BehavStratSpace, const term_order &Lex, + const VariableSpace &BehavStratSpace, const BehaviorSupportProfile &little_supp, const BehaviorSupportProfile &big_supp, const std::map &var_index) { - gPolyList answer(&BehavStratSpace, &Lex); - answer += ActionProbsSumToOneIneqs(p_solution, BehavStratSpace, Lex, big_supp, var_index); - answer += ANFExpectedPayoffDiffPolys(p_solution, BehavStratSpace, Lex, little_supp, big_supp, - var_index); + gPolyList answer(&BehavStratSpace); + answer += ActionProbsSumToOneIneqs(p_solution, BehavStratSpace, big_supp, var_index); + answer += + ANFExpectedPayoffDiffPolys(p_solution, BehavStratSpace, little_supp, big_supp, var_index); return answer; } @@ -448,13 +446,10 @@ bool ExtendsToAgentNash(const MixedBehaviorProfile &p_solution, } // We establish the space - gSpace BehavStratSpace(num_vars); - ORD_PTR ptr = &lex; - term_order Lex(&BehavStratSpace, ptr); - + VariableSpace BehavStratSpace(num_vars); num_vars = BehavStratSpace.Dmnsn(); gPolyList inequalities = - ExtendsToANFNashIneqs(p_solution, BehavStratSpace, Lex, little_supp, big_supp, var_index); + ExtendsToANFNashIneqs(p_solution, BehavStratSpace, little_supp, big_supp, var_index); // set up the rectangle of search Vector bottoms(num_vars), tops(num_vars); diff --git a/src/solvers/enumpoly/efgpoly.cc b/src/solvers/enumpoly/efgpoly.cc index 03d5537e5..c4bf5200d 100644 --- a/src/solvers/enumpoly/efgpoly.cc +++ b/src/solvers/enumpoly/efgpoly.cc @@ -53,8 +53,7 @@ namespace { class ProblemData { public: GameSequenceForm sfg; - gSpace Space; - term_order Lex; + VariableSpace Space; std::map var; std::map> variables; @@ -65,13 +64,13 @@ gPoly BuildSequenceVariable(ProblemData &p_data, const GameSequence &p_s const std::map &var) { if (!p_sequence->action) { - return {&p_data.Space, 1, &p_data.Lex}; + return {&p_data.Space, 1}; } if (p_sequence->action != p_data.sfg.GetSupport().GetActions(p_sequence->GetInfoset()).back()) { - return {&p_data.Space, var.at(p_sequence), 1, &p_data.Lex}; + return {&p_data.Space, var.at(p_sequence), 1}; } - gPoly equation(&p_data.Space, &p_data.Lex); + gPoly equation(&p_data.Space); for (auto seq : p_data.sfg.GetSequences(p_sequence->player)) { if (seq == p_sequence) { continue; @@ -86,8 +85,7 @@ gPoly BuildSequenceVariable(ProblemData &p_data, const GameSequence &p_s ProblemData::ProblemData(const BehaviorSupportProfile &p_support) : sfg(p_support), - Space(sfg.GetSequences().size() - sfg.GetInfosets().size() - sfg.GetPlayers().size()), - Lex(&Space, lex) + Space(sfg.GetSequences().size() - sfg.GetInfosets().size() - sfg.GetPlayers().size()) { for (auto sequence : sfg.GetSequences()) { if (sequence->action && @@ -103,12 +101,12 @@ ProblemData::ProblemData(const BehaviorSupportProfile &p_support) gPoly GetPayoff(ProblemData &p_data, const GamePlayer &p_player) { - gPoly equation(&p_data.Space, &p_data.Lex); + gPoly equation(&p_data.Space); for (auto profile : p_data.sfg.GetContingencies()) { auto pay = p_data.sfg.GetPayoff(profile, p_player); if (pay != Rational(0)) { - gPoly term(&p_data.Space, double(pay), &p_data.Lex); + gPoly term(&p_data.Space, double(pay)); for (auto player : p_data.sfg.GetPlayers()) { term *= p_data.variables.at(profile[player]); } @@ -160,7 +158,7 @@ std::list> SolveSupport(const BehaviorSupportProfil bool &p_isSingular, int p_stopAfter) { ProblemData data(p_support); - gPolyList equations(&data.Space, &data.Lex); + gPolyList equations(&data.Space); IndifferenceEquations(data, equations); LastActionProbPositiveInequalities(data, equations); diff --git a/src/solvers/enumpoly/gpoly.cc b/src/solvers/enumpoly/gpoly.cc index 41c90646b..e7a8374e1 100644 --- a/src/solvers/enumpoly/gpoly.cc +++ b/src/solvers/enumpoly/gpoly.cc @@ -23,28 +23,10 @@ #include "gpoly.imp" #include "gambit.h" -template <> double gPoly::String_Coeff(double nega) -{ - std::string Coeff; - while ((charc >= '0' && charc <= '9') || charc == '.') { - Coeff += charc; - charnum++; - GetChar(); - } - if (Coeff.empty()) { - return nega; - } - else { - return (nega * strtod(Coeff.c_str(), nullptr)); - } -} - template class gPoly; template gPoly operator+(const gPoly &poly, const double &val); template gPoly operator*(const double &val, const gPoly &poly); template gPoly operator*(const gPoly &poly, const double &val); -template gPoly TogDouble(const gPoly &); +template gPoly ToDouble(const gPoly &); template gPoly NormalizationOfPoly(const gPoly &); - -template std::string &operator<<(std::string &, const gPoly &); diff --git a/src/solvers/enumpoly/gpoly.h b/src/solvers/enumpoly/gpoly.h index 56bb16324..28dd2fab7 100644 --- a/src/solvers/enumpoly/gpoly.h +++ b/src/solvers/enumpoly/gpoly.h @@ -25,38 +25,215 @@ #include "gambit.h" #include "core/sqmatrix.h" -#include "monomial.h" -#include "poly.h" -// These classes are used to store and mathematically manipulate polynomials. +using namespace Gambit; + +class VariableSpace { +public: + struct Variable { + std::string name; + int number; + }; + + explicit VariableSpace(size_t nvars) + { + for (size_t i = 1; i <= nvars; i++) { + m_variables.push_back({"n" + std::to_string(i), static_cast(i)}); + } + } + VariableSpace(const VariableSpace &) = delete; + ~VariableSpace() = default; + VariableSpace &operator=(const VariableSpace &) = delete; + const Variable &operator[](const int index) const { return m_variables[index]; } + int Dmnsn() const { return m_variables.size(); } + +private: + Array m_variables; +}; + +// An exponent vector is a vector of integers representing the exponents on variables in +// a space in a monomial. +// +// Exponent vectors are ordered lexicographically. +class ExponentVector { +private: + const VariableSpace *m_space; + Vector m_components; + +public: + explicit ExponentVector(const VariableSpace *p) : m_space(p), m_components(p->Dmnsn()) + { + m_components = 0; + } + // Construct x_i^j + ExponentVector(const VariableSpace *p, const int i, const int j) + : m_space(p), m_components(p->Dmnsn()) + { + m_components = 0; + m_components[i] = j; + } + ExponentVector(const VariableSpace *space, const Vector &exps) + : m_space(space), m_components(exps) + { + } + ExponentVector(const ExponentVector &) = default; + ~ExponentVector() = default; + + // Operators + ExponentVector &operator=(const ExponentVector &) = default; + + int operator[](int index) const { return m_components[index]; } + + bool operator==(const ExponentVector &y) const + { + return m_space == y.m_space && m_components == y.m_components; + } + bool operator!=(const ExponentVector &y) const + { + return m_space != y.m_space || m_components != y.m_components; + } + ExponentVector operator+(const ExponentVector &v) const + { + ExponentVector tmp(*this); + tmp.m_components += v.m_components; + return tmp; + } + + // Other operations + ExponentVector WithZeroExponent(const int varnumber) const + { + ExponentVector tmp(*this); + tmp.m_components[varnumber] = 0; + return tmp; + } + ExponentVector WithDecrementedExponent(const int varnumber) const + { + ExponentVector tmp(*this); + tmp.m_components[varnumber]--; + return tmp; + } + + // Information + int Dmnsn() const { return m_space->Dmnsn(); } + bool IsConstant() const + { + for (int i = 1; i <= Dmnsn(); i++) { + if ((*this)[i] > 0) { + return false; + } + } + return true; + } + bool IsMultiaffine() const + { + for (int i = 1; i <= Dmnsn(); i++) { + if ((*this)[i] > 1) { + return false; + } + } + return true; + } + int TotalDegree() const + { + int exp_sum = 0; + for (int i = 1; i <= Dmnsn(); i++) { + exp_sum += (*this)[i]; + } + return exp_sum; + } + + // Manipulation + void ToZero() + { + for (int i = 1; i <= Dmnsn(); i++) { + m_components[i] = 0; + } + } + + bool operator<(const ExponentVector &y) const + { + for (int i = 1; i <= Dmnsn(); i++) { + if (m_components[i] < y.m_components[i]) { + return true; + } + if (m_components[i] > y.m_components[i]) { + return false; + } + } + return false; + } + bool operator<=(const ExponentVector &y) const { return *this < y || *this == y; } + bool operator>(const ExponentVector &y) const { return !(*this <= y); } + bool operator>=(const ExponentVector &y) const { return !(*this < y); } +}; -// **NOTE** -// Every type T to be used needs a procedure to convert a gText coefficient -// to the type T for the gText SOP input form and a procedure to convert -// the coefficient into a gText for the SOP output form. +/// A monomial of multiple variables with non-negative exponents +template class gMono { +private: + T coef; + ExponentVector exps; -// ******************* -// gPoly declaration -// ******************* +public: + // constructors + gMono(const VariableSpace *p, const T &x) : coef(x), exps(p) {} + gMono(const T &x, const ExponentVector &e) : coef(x), exps(e) + { + if (x == static_cast(0)) { + exps.ToZero(); + } + } + gMono(const gMono &) = default; + ~gMono() = default; + + // operators + gMono &operator=(const gMono &) = default; + + bool operator==(const gMono &y) const { return (coef == y.coef && exps == y.exps); } + bool operator!=(const gMono &y) const { return (coef != y.coef || exps != y.exps); } + gMono operator*(const gMono &y) const { return {coef * y.coef, exps + y.exps}; } + gMono operator+(const gMono &y) const { return {coef + y.coef, exps}; } + gMono &operator+=(const gMono &y) + { + coef += y.coef; + return *this; + } + gMono &operator*=(const T &v) + { + coef *= v; + return *this; + } + gMono operator-() const { return {-coef, exps}; } + + // information + const T &Coef() const { return coef; } + int Dmnsn() const { return exps.Dmnsn(); } + int TotalDegree() const { return exps.TotalDegree(); } + bool IsConstant() const { return exps.IsConstant(); } + bool IsMultiaffine() const { return exps.IsMultiaffine(); } + const ExponentVector &ExpV() const { return exps; } + T Evaluate(const Vector &vals) const + { + T answer = coef; + for (int i = 1; i <= exps.Dmnsn(); i++) { + for (int j = 1; j <= exps[i]; j++) { + answer *= vals[i]; + } + } + return answer; + } +}; +// These classes are used to store and mathematically manipulate polynomials. template class gPoly { private: - const gSpace *Space; // pointer to variable Space of space - const term_order *Order; + const VariableSpace *Space; // pointer to variable Space of space Gambit::List> Terms; // alternative implementation - // used for gText parsing; - unsigned int charnum; - char charc; - std::string TheString; - //---------------------- // some private members //---------------------- - // Information - exp_vect OrderMaxMonomialDivisibleBy(const term_order &order, const exp_vect &expv); // Arithmetic Gambit::List> Adder(const Gambit::List> &, const Gambit::List> &) const; @@ -68,45 +245,23 @@ template class gPoly { gPoly TranslateOfMono(const gMono &, const Gambit::Vector &) const; gPoly MonoInNewCoordinates(const gMono &, const Gambit::SquareMatrix &) const; - //----------------------------------------------- - // Going back and forth from std::strings to gPoly's - //----------------------------------------------- - - // std::string input parser functions - void String_Term(T nega); - T String_Coeff(T nega); - int String_GetPow(); - void String_VarAndPow(Gambit::Array &PowArray); - void GetChar(); - // Is the string a valid polynomial? - bool Check_String(const std::string &Hold); - - //---------------------- - // private friends - //---------------------- - - // friend gPoly operator*<>(const gPoly &poly, const T val); - // friend gPoly operator*(const T val, const gPoly &poly); - public: //--------------------------- // Construction, destruction: //--------------------------- // Null gPoly constructor - gPoly(const gSpace *, const term_order *); - // Constructs a gPoly equal to the SOP representation in the std::string - gPoly(const gSpace *, const std::string &, const term_order *); + gPoly(const VariableSpace *); // Constructs a constant gPoly - gPoly(const gSpace *, const T &, const term_order *); + gPoly(const VariableSpace *, const T &); // Constructs a gPoly equal to another; gPoly(const gPoly &); // Constructs a gPoly that is x_{var_no}^exp; - gPoly(const gSpace *p, int var_no, int exp, const term_order *); + gPoly(const VariableSpace *p, int var_no, int exp); // Constructs a gPoly that is the monomial coeff*vars^exps; - gPoly(const gSpace *p, exp_vect exps, T coeff, const term_order *); + gPoly(const VariableSpace *p, ExponentVector exps, T coeff); // Constructs a gPoly with single monomial - gPoly(const gSpace *p, const gMono &, const term_order *); + gPoly(const VariableSpace *p, const gMono &); ~gPoly() = default; @@ -115,7 +270,6 @@ template class gPoly { //---------- gPoly &operator=(const gPoly &); - gPoly &operator=(const std::string &); // Set polynomial equal to the SOP form in the string gPoly operator-() const; gPoly operator-(const gPoly &) const; @@ -136,60 +290,31 @@ template class gPoly { // Information: //------------- - const gSpace *GetSpace() const; - const term_order *GetOrder() const; + const VariableSpace *GetSpace() const; int Dmnsn() const; - bool IsZero() const; int DegreeOfVar(int var_no) const; int Degree() const; - T GetCoef(const Gambit::Array &Powers) const; - T GetCoef(const exp_vect &Powers) const; + T GetCoef(const Gambit::Vector &Powers) const; + T GetCoef(const ExponentVector &Powers) const; gPoly LeadingCoefficient(int varnumber) const; T NumLeadCoeff() const; // deg == 0 bool IsConstant() const; bool IsMultiaffine() const; - int UniqueActiveVariable() const; - // returns 0 if constant, -1 if truly multivariate - polynomial UnivariateEquivalent(int activar) const; // assumes UniqueActiveVariable() is true - T Evaluate(const Gambit::Array &values) const; - gPoly EvaluateOneVar(int varnumber, T val) const; + T Evaluate(const Gambit::Vector &values) const; gPoly PartialDerivative(int varnumber) const; - int No_Monomials() const; - Gambit::List ExponentVectors() const; Gambit::List> MonomialList() const; gPoly TranslateOfPoly(const Gambit::Vector &) const; gPoly PolyInNewCoordinates(const Gambit::SquareMatrix &) const; T MaximalValueOfNonlinearPart(const T &) const; - - //-------------------- - // Term Order Concepts - //-------------------- - - exp_vect LeadingPowerProduct(const term_order &) const; - T LeadingCoefficient(const term_order &) const; - gPoly LeadingTerm(const term_order &) const; - void ToMonic(const term_order &); - void ReduceByDivisionAtExpV(const term_order &, const gPoly &, const exp_vect &); - void ReduceByRepeatedDivision(const term_order &, const gPoly &); - gPoly S_Polynomial(const term_order &, const gPoly &) const; - - //--------------- - // Printing Stuff - //--------------- - - // Print polynomial in SOP form - void Output(std::string &) const; }; -template std::string &operator<<(std::string &, const gPoly &); - //------------- // Conversion: //------------- -template gPoly TogDouble(const gPoly &); +template gPoly ToDouble(const gPoly &); template gPoly NormalizationOfPoly(const gPoly &); // global multiply by scalar operators @@ -200,6 +325,4 @@ template gPoly operator*(const gPoly &poly, const T &val); template gPoly operator+(const T &val, const gPoly &poly); template gPoly operator+(const gPoly &poly, const T &val); -template std::string ToText(const gPoly &p); - #endif // # GPOLY_H diff --git a/src/solvers/enumpoly/gpoly.imp b/src/solvers/enumpoly/gpoly.imp index 6d0ab3ca5..ebe531faf 100644 --- a/src/solvers/enumpoly/gpoly.imp +++ b/src/solvers/enumpoly/gpoly.imp @@ -20,7 +20,6 @@ // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // -#include // for abs() #include // for std::max() #include "gpoly.h" #include "gambit.h" @@ -33,50 +32,31 @@ // Constructors / Destructor //--------------------------- -template -gPoly::gPoly(const gSpace *p, const term_order *o) : Space(p), Order(o), Terms() -{ -} +template gPoly::gPoly(const VariableSpace *p) : Space(p) {} -template -gPoly::gPoly(const gSpace *p, const T &constant, const term_order *o) - : Space(p), Order(o), Terms() +template gPoly::gPoly(const VariableSpace *p, const T &constant) : Space(p) { if (constant != (T)0) { Terms.push_back(gMono(p, constant)); } } -template -gPoly::gPoly(const gSpace *p, const std::string &s, const term_order *o) - : Space(p), Order(o), Terms() +template gPoly::gPoly(const VariableSpace *p, int var_no, int exp) : Space(p) { - *this = s; // Operator = needs to be fixed + Terms.push_back(gMono((T)1, ExponentVector(p, var_no, exp))); } -template -gPoly::gPoly(const gSpace *p, int var_no, int exp, const term_order *o) - : Space(p), Order(o), Terms() -{ - Terms.push_back(gMono((T)1, exp_vect(p, var_no, exp))); -} - -template -gPoly::gPoly(const gSpace *p, exp_vect exps, T coeff, const term_order *o) - : Space(p), Order(o), Terms() +template gPoly::gPoly(const VariableSpace *p, ExponentVector exps, T coeff) : Space(p) { Terms.push_back(gMono(coeff, exps)); } -template -gPoly::gPoly(const gSpace *p, const gMono &mono, const term_order *o) - : Space(p), Order(o), Terms() +template gPoly::gPoly(const VariableSpace *p, const gMono &mono) : Space(p) { Terms.push_back(mono); } -template -gPoly::gPoly(const gPoly &p) : Space(p.Space), Order(p.Order), Terms(p.Terms) +template gPoly::gPoly(const gPoly &p) : Space(p.Space), Terms(p.Terms) { *this = p; } @@ -93,64 +73,6 @@ template gPoly &gPoly::operator=(const gPoly &p) return (*this); } -template gPoly &gPoly::operator=(const std::string &Hold) -{ - Gambit::List> nullTerms; - Terms = nullTerms; // get rid of old Terms - - charnum = 0; - int contflag = 1; - T nega = 1; - Gambit::Array PowArray(Space->Dmnsn()); - TheString = Hold + " +"; - - charc = TheString[charnum]; - - while (charnum <= TheString.length() && contflag) { - switch (charc) { - case '+': - case ' ': - charnum++; - charc = TheString[charnum]; - break; - case '-': - charnum++; - charc = TheString[charnum]; - nega = -nega; - break; - case 0: // Null termination of string - contflag = 0; - break; - default: - String_Term(nega); - nega = T(1); - break; - } - } - - Gambit::List> newTerms; - for (int j = 1; j <= Terms.size(); j++) { - int low = 0; - int high = newTerms.size() + 1; - while (low + 1 < high) { - int test = low + (high - low) / 2; - if (1 <= test && test <= newTerms.size()) { - // assert (Terms[j].ExpV() != newTerms[test].ExpV()); - } - if (Order->Less(Terms[j].ExpV(), newTerms[test].ExpV())) { - high = test; - } - else { - low = test; - } - } - newTerms.insert(std::next(newTerms.begin(), high - 1), Terms[j]); - } - Terms = newTerms; - - return (*this); -} - template gPoly gPoly::operator-() const { gPoly neg(*this); @@ -192,10 +114,7 @@ template void gPoly::operator+=(const gPoly &p) Terms = Adder(Terms, p.Terms); } -template void gPoly::operator+=(const T &val) -{ - *this += gPoly(Space, val, Order); -} +template void gPoly::operator+=(const T &val) { *this += gPoly(Space, val); } template gPoly gPoly::operator*(const gPoly &p) const { @@ -247,181 +166,14 @@ template bool gPoly::operator==(const gPoly &p) const template bool gPoly::operator!=(const gPoly &p) const { return !(*this == p); } //---------------------------------- -// Member Functions +// Information //---------------------------------- -template void gPoly::String_Term(T nega) -{ - Gambit::Array PowArray(Dmnsn()); - for (int a = 1; a <= Dmnsn(); a++) { - PowArray[a] = 0; - } - T val; - val = String_Coeff(nega); - - while (charc != '+' && charc != '-') { - if (charc == ' ') { - charnum++; - charc = TheString[charnum]; - } - else { - String_VarAndPow(PowArray); - } - } - - Terms.push_back(gMono(val, exp_vect(Space, PowArray))); -} - -template int gPoly::String_GetPow() +template const VariableSpace *gPoly::GetSpace() const { - - std::string Pow = ""; - while (charc == ' ') { - charnum++; - charc = TheString[charnum]; - } - - while (charc >= '0' && charc <= '9') { - Pow += charc; - charnum++; - charc = TheString[charnum]; - } - return (atoi(Pow.c_str())); + return (VariableSpace *)Space; } -template void gPoly::String_VarAndPow(Gambit::Array &PowArray) -{ - std::string VarName = ""; - int pow, varname; - while (charc != '^' && charc != ' ') { - VarName += charc; - charnum++; - charc = TheString[charnum]; - } - if (charc == '^') { - charnum++; - charc = TheString[charnum]; - pow = String_GetPow(); - } - else { - pow = 1; - } - for (varname = 1; varname <= Dmnsn() && VarName != (Space->VariableWithNumber(varname))->Name; - varname++) - ; - if (varname <= Dmnsn()) { - PowArray[varname] = pow; - } -} - -// bool function to check the string in &Hold - -template bool gPoly::Check_String(const std::string &Hold) -{ - unsigned int charnum = 0; - int boolflag = 0; - // state variables - int statenumber = 0; - int statevar = 0; - int statesign = 1; - // values of the state variables - enum number { nonumberbef, numberbef }; - enum var { novarbef, varbef }; - enum sign { nosignbef, signbef }; - std::string TheString = Hold; - char charc = TheString[charnum]; - // movement along the string with a switch case - while (charnum < TheString.length()) { - switch (charc) { - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - statenumber = numberbef; - statesign = nosignbef; - break; - case 'n': - if (statenumber == 0 && statesign == 0) { - boolflag++; - } - if (charnum == (TheString.length() - 1)) { - boolflag++; - } - statenumber = number(0); - statevar = varbef; - statesign = nosignbef; - break; - case '^': - if (statenumber == 0) { - boolflag++; - } - if (statevar == 0) { - boolflag++; - } - if (charnum == (TheString.length() - 1)) { - boolflag++; - } - statenumber = nonumberbef; - statevar = novarbef; - statesign = nosignbef; - break; - case '/': - case '.': - if (statenumber == 0) { - boolflag++; - } - if (charnum == (TheString.length() - 1)) { - boolflag++; - } - statenumber = nonumberbef; - statesign = nosignbef; - break; - case '+': - case '-': - if (statenumber == 0 && charnum != 0) { - boolflag++; - } - if (charnum == (TheString.length() - 1)) { - boolflag++; - } - statenumber = nonumberbef; - statevar = novarbef; - statesign = signbef; - break; - case ' ': - break; - default: - boolflag++; - break; - } - charnum++; - charc = TheString[charnum]; - } - // return values - if (boolflag == 0) { - return true; - } - else { - return false; - } -} - -template void gPoly::GetChar() { charc = TheString[charnum]; } - -//---------------------------------- -// Information -//---------------------------------- - -template const gSpace *gPoly::GetSpace() const { return (gSpace *)Space; } - -template const term_order *gPoly::GetOrder() const { return (term_order *)Order; } - template int gPoly::Dmnsn() const { return Space->Dmnsn(); } template int gPoly::DegreeOfVar(int var_no) const @@ -435,8 +187,6 @@ template int gPoly::DegreeOfVar(int var_no) const return max; } -template bool gPoly::IsZero() const { return Terms.empty(); } - template int gPoly::Degree() const { int max = 0; @@ -448,12 +198,12 @@ template int gPoly::Degree() const return max; } -template T gPoly::GetCoef(const Gambit::Array &Powers) const +template T gPoly::GetCoef(const Gambit::Vector &Powers) const { - return GetCoef(exp_vect(Space, Powers)); + return GetCoef(ExponentVector(Space, Powers)); } -template T gPoly::GetCoef(const exp_vect &Powers) const +template T gPoly::GetCoef(const ExponentVector &Powers) const { for (int j = 1; j <= Terms.size(); j++) { if (Terms[j].ExpV() == Powers) { @@ -493,7 +243,7 @@ template bool gPoly::IsMultiaffine() const return true; } -template T gPoly::Evaluate(const Gambit::Array &values) const +template T gPoly::Evaluate(const Gambit::Vector &values) const { T answer = 0; for (const auto &term : Terms) { @@ -502,57 +252,8 @@ template T gPoly::Evaluate(const Gambit::Array &values) const return answer; } -template Gambit::List gPoly::ExponentVectors() const -{ - Gambit::List result; - for (const auto &v : Terms) { - result.push_back(exp_vect(v.ExpV())); - } - return result; -} - template Gambit::List> gPoly::MonomialList() const { return Terms; } -template int gPoly::No_Monomials() const { return Terms.size(); } - -template int gPoly::UniqueActiveVariable() const -{ - Gambit::List ExpVecs = ExponentVectors(); - int activar = 0; - for (int i = 1; i <= ExpVecs.size(); i++) { - for (int j = 1; j <= Dmnsn(); j++) { - if (ExpVecs[i][j] > 0) { - if (activar > 0 && activar != j) { - return -1; // multivariate! - } - else { - activar = j; - } - } - } - } - return activar; -} - -template polynomial gPoly::UnivariateEquivalent(int activar) const -{ - // assert(UniqueActiveVariable() >= 0); - - Gambit::List coefs; - - if (!IsZero()) { - for (int h = 0; h <= DegreeOfVar(activar); h++) { - coefs.push_back((T)0); - } - - for (int i = 1; i <= Terms.size(); i++) { - coefs[Terms[i].ExpV()[activar] + 1] = Terms[i].Coef(); - } - } - - return polynomial(coefs); -} - //------------------------------------------------------------- // Private Versions of Arithmetic Operators //------------------------------------------------------------- @@ -582,11 +283,11 @@ Gambit::List> gPoly::Adder(const Gambit::List> &One, i++; } else { - if (Order->Less(One[i].ExpV(), Two[j].ExpV())) { + if (One[i].ExpV() < Two[j].ExpV()) { answer.push_back(One[i]); i++; } - else if (Order->Greater(One[i].ExpV(), Two[j].ExpV())) { + else if (One[i].ExpV() > Two[j].ExpV()) { answer.push_back(Two[j]); j++; } @@ -625,10 +326,10 @@ Gambit::List> gPoly::Mult(const Gambit::List> &One, else { int bot = 1; int top = answer.size(); - if (Order->Less(answer[top].ExpV(), next.ExpV())) { + if (answer[top].ExpV() < next.ExpV()) { answer.push_back(next); } - else if (Order->Greater(answer[bot].ExpV(), next.ExpV())) { + else if (answer[bot].ExpV() > next.ExpV()) { answer.push_front(next); } else { @@ -644,10 +345,10 @@ Gambit::List> gPoly::Mult(const Gambit::List> &One, if (answer[test].ExpV() == next.ExpV()) { bot = top = test; } - else if (Order->Less(answer[test].ExpV(), next.ExpV())) { + else if (answer[test].ExpV() < next.ExpV()) { bot = test; } - else { // (Order->Greater(answer[test].ExpV(),next.ExpV())) + else { // (answer[test].ExpV() > next.ExpV()) top = test; } } @@ -667,13 +368,11 @@ Gambit::List> gPoly::Mult(const Gambit::List> &One, template gPoly gPoly::DivideByPolynomial(const gPoly &den) const { - gPoly zero(Space, (T)0, Order); + gPoly zero(Space, (T)0); if (den == zero) { throw Gambit::ZeroDivideException(); } - // assert(*this == zero || den.Degree() <= Degree()); - // assumes exact divisibility! gPoly result = zero; @@ -695,8 +394,7 @@ template gPoly gPoly::DivideByPolynomial(const gPoly &den) co while (remainder != zero) { gPoly quot = remainder.LeadingCoefficient(last) / den.LeadingCoefficient(last); - gPoly power_of_last(Space, last, remainder.DegreeOfVar(last) - den.DegreeOfVar(last), - Order); + gPoly power_of_last(Space, last, remainder.DegreeOfVar(last) - den.DegreeOfVar(last)); result += quot * power_of_last; remainder -= quot * power_of_last * den; } @@ -704,39 +402,13 @@ template gPoly gPoly::DivideByPolynomial(const gPoly &den) co return result; } -template gPoly gPoly::EvaluateOneVar(int varnumber, T val) const -{ - gPoly answer(Space, (T)0, Order); - - for (int i = 1; i <= Terms.size(); i++) { - answer += - gPoly(Space, Terms[i].ExpV().AfterZeroingOutExpOfVariable(varnumber), - ((T)Terms[i].Coef()) * ((T)pow(val, (double)Terms[i].ExpV()[varnumber])), Order); - } - return answer; -} - -template -exp_vect gPoly::OrderMaxMonomialDivisibleBy(const term_order &order, const exp_vect & /*expv*/) -{ - // gout << "You have just tested OrderMaxMonomialDivisibleBy.\n"; - - exp_vect answer(Space); // constructs [0,..,0] - for (int i = 1; i <= Terms.size(); i++) { - if (order.Less(answer, Terms[i].ExpV()) && answer < Terms[i].ExpV()) { - answer = Terms[i].ExpV(); - } - } - return answer; -} - template gPoly gPoly::PartialDerivative(int varnumber) const { gPoly newPoly(*this); for (int i = 1; i <= newPoly.Terms.size(); i++) { newPoly.Terms[i] = gMono(newPoly.Terms[i].Coef() * (T)newPoly.Terms[i].ExpV()[varnumber], - newPoly.Terms[i].ExpV().AfterDecrementingExpOfVariable(varnumber)); + newPoly.Terms[i].ExpV().WithZeroExponent(varnumber)); } int j = 1; @@ -762,117 +434,22 @@ template gPoly gPoly::LeadingCoefficient(int varnumber) const for (int j = 1; j <= Terms.size(); j++) { if (Terms[j].ExpV()[varnumber] == degree) { newPoly.Terms.push_back( - gMono(Terms[j].Coef(), Terms[j].ExpV().AfterZeroingOutExpOfVariable(varnumber))); + gMono(Terms[j].Coef(), Terms[j].ExpV().WithZeroExponent(varnumber))); } } return (newPoly); } -//-------------------- -// Term Order Concepts -//-------------------- - -template exp_vect gPoly::LeadingPowerProduct(const term_order &order) const -{ - // assert (Terms.Length() > 0); - - if (*Order == order) { // worth a try ... - return Terms.back().ExpV(); - } - else { - int max = 1; - for (int j = 2; j <= Terms.size(); j++) { - if (order.Less(Terms[max].ExpV(), Terms[j].ExpV())) { - max = j; - } - } - return Terms[max].ExpV(); - } -} - -template T gPoly::LeadingCoefficient(const term_order &order) const -{ - if (*Order == order) { // worth a try ... - return Terms.back().Coef(); - } - else { - int max = 1; - for (int j = 2; j <= Terms.size(); j++) { - if (order.Less(Terms[max].ExpV(), Terms[j].ExpV())) { - max = j; - } - } - return Terms[max].Coef(); - } -} - -template gPoly gPoly::LeadingTerm(const term_order &order) const -{ - if (*Order == order) { // worth a try ... - return gPoly(Space, Terms.back(), Order); - } - else { - int max = 1; - for (int j = 2; j <= Terms.size(); j++) { - if (order.Less(Terms[max].ExpV(), Terms[j].ExpV())) { - max = j; - } - } - return gPoly(Space, Terms[max], Order); - } -} - -template void gPoly::ToMonic(const term_order &order) -{ - *this = *this / LeadingCoefficient(order); -} - -template -void gPoly::ReduceByDivisionAtExpV(const term_order &order, const gPoly &divisor, - const exp_vect &expv) -{ - // assert(expv >= divisor.LeadingPowerProduct(order)); - // assert(divisor.LeadingCoefficient(order) != (T)0); - - gPoly factor(Space, expv - divisor.LeadingPowerProduct(order), (T)1, Order); - - *this -= (GetCoef(expv) / divisor.LeadingCoefficient(order)) * factor * divisor; -} - -template -void gPoly::ReduceByRepeatedDivision(const term_order &order, const gPoly &divisor) -{ - exp_vect zero_exp_vec(Space); - - exp_vect exps = OrderMaxMonomialDivisibleBy(order, divisor.LeadingPowerProduct(order)); - - while (exps != zero_exp_vec) { - ReduceByDivisionAtExpV(order, divisor, exps); - exps = OrderMaxMonomialDivisibleBy(order, divisor.LeadingPowerProduct(order)); - } -} - -template -gPoly gPoly::S_Polynomial(const term_order &order, const gPoly &arg2) const -{ - exp_vect exp_lcm = (LeadingPowerProduct(order)).LCM(arg2.LeadingPowerProduct(order)); - gPoly lcm = gPoly(Space, exp_lcm, (T)1, Order); - gPoly L1 = lcm / LeadingTerm(order); - gPoly L2 = lcm / arg2.LeadingTerm(order); - - return L1 * (*this) - L2 * arg2; -} - template gPoly gPoly::TranslateOfMono(const gMono &m, const Gambit::Vector &new_origin) const { - gPoly answer(GetSpace(), (T)1, GetOrder()); + gPoly answer(GetSpace(), (T)1); for (int i = 1; i <= Dmnsn(); i++) { if (m.ExpV()[i] > 0) { - gPoly lt(GetSpace(), i, 1, GetOrder()); - lt += gPoly(GetSpace(), new_origin[i], GetOrder()); + gPoly lt(GetSpace(), i, 1); + lt += gPoly(GetSpace(), new_origin[i]); for (int j = 1; j <= m.ExpV()[i]; j++) { answer *= lt; } @@ -886,7 +463,7 @@ gPoly gPoly::TranslateOfMono(const gMono &m, const Gambit::Vector &n template gPoly gPoly::TranslateOfPoly(const Gambit::Vector &new_origin) const { - gPoly answer(GetSpace(), GetOrder()); + gPoly answer(GetSpace()); for (int i = 1; i <= this->MonomialList().size(); i++) { answer += TranslateOfMono(this->MonomialList()[i], new_origin); } @@ -898,14 +475,14 @@ gPoly gPoly::MonoInNewCoordinates(const gMono &m, const Gambit::SquareM { // assert(M.NumRows() == Dmnsn()); - gPoly answer(GetSpace(), (T)1, GetOrder()); + gPoly answer(GetSpace(), (T)1); for (int i = 1; i <= Dmnsn(); i++) { if (m.ExpV()[i] > 0) { - gPoly linearform(GetSpace(), (T)0, GetOrder()); + gPoly linearform(GetSpace(), (T)0); for (int j = 1; j <= Dmnsn(); j++) { - exp_vect exps(GetSpace(), j, 1); - linearform += gPoly(GetSpace(), exps, M(i, j), GetOrder()); + ExponentVector exps(GetSpace(), j, 1); + linearform += gPoly(GetSpace(), exps, M(i, j)); } for (int k = 1; k <= m.ExpV()[i]; k++) { answer *= linearform; @@ -920,7 +497,7 @@ gPoly gPoly::MonoInNewCoordinates(const gMono &m, const Gambit::SquareM template gPoly gPoly::PolyInNewCoordinates(const Gambit::SquareMatrix &M) const { - gPoly answer(GetSpace(), GetOrder()); + gPoly answer(GetSpace()); for (int i = 1; i <= MonomialList().size(); i++) { answer += MonoInNewCoordinates(MonomialList()[i], M); } @@ -961,74 +538,17 @@ template gPoly operator+(const T &val, const gPoly &poly) template gPoly operator+(const gPoly &poly, const T &val) { return val + poly; } -template void gPoly::Output(std::string &t) const -{ - std::string s; - if (Terms.empty()) { - s += "0"; - } - else { - for (int j = 1; j <= Terms.size(); j++) { - if (Terms[j].Coef() < (T)0) { - s += "-"; - if (j > 1) { - s += " "; - } - } - else if (j > 1) { - s += "+ "; - } - - if ((Terms[j].Coef() != (T)1 && -Terms[j].Coef() != (T)1) || Terms[j].IsConstant()) { - if (Terms[j].Coef() < (T)0) { - s += Gambit::lexical_cast(-Terms[j].Coef()); - } - else { - s += Gambit::lexical_cast(Terms[j].Coef()); - } - - for (int k = 1; k <= Space->Dmnsn(); k++) { - int exp = Terms[j].ExpV()[k]; - if (exp > 0) { - s += " "; - s += (*Space)[k]->Name; - if (exp != 1) { - s += '^'; - s += Gambit::lexical_cast(exp); - } - } - } - - if (j < Terms.size()) { - s += " "; - } - } - } - } - if (s == "") { - s = " 0"; - } - - t += s; -} - -template std::string &operator<<(std::string &p_text, const gPoly &p_poly) -{ - p_poly.Output(p_text); - return p_text; -} - //---------------------------------- // Conversion //---------------------------------- -template gPoly TogDouble(const gPoly &given) +template gPoly ToDouble(const gPoly &given) { - gPoly answer(given.GetSpace(), given.GetOrder()); + gPoly answer(given.GetSpace()); Gambit::List> list = given.MonomialList(); for (int i = 1; i <= list.size(); i++) { auto nextcoef = (double)list[i].Coef(); - gPoly next(given.GetSpace(), list[i].ExpV(), nextcoef, given.GetOrder()); + gPoly next(given.GetSpace(), list[i].ExpV(), nextcoef); answer += next; } @@ -1044,13 +564,13 @@ template gPoly NormalizationOfPoly(const gPoly &given) } if (maxcoeff < 0.000000001) { - return TogDouble(given); + return ToDouble(given); } - gPoly answer(given.GetSpace(), given.GetOrder()); + gPoly answer(given.GetSpace()); for (int i = 1; i <= list.size(); i++) { double nextcoef = (double)list[i].Coef() / maxcoeff; - gPoly next(given.GetSpace(), list[i].ExpV(), nextcoef, given.GetOrder()); + gPoly next(given.GetSpace(), list[i].ExpV(), nextcoef); answer += next; } diff --git a/src/solvers/enumpoly/gpolylst.h b/src/solvers/enumpoly/gpolylst.h index 6569c7f78..87a98e20f 100644 --- a/src/solvers/enumpoly/gpolylst.h +++ b/src/solvers/enumpoly/gpolylst.h @@ -29,15 +29,14 @@ template class gPolyList { private: - const gSpace *Space; - const term_order *Order; + const VariableSpace *Space; Gambit::List *> List; public: - gPolyList(const gSpace *sp, const term_order *to) : Space(sp), Order(to) {} + gPolyList(const VariableSpace *sp) : Space(sp) {} - gPolyList(const gSpace *, const term_order *, const Gambit::List *> &); - gPolyList(const gSpace *, const term_order *, const Gambit::List> &); + gPolyList(const VariableSpace *, const Gambit::List *> &); + gPolyList(const VariableSpace *, const Gambit::List> &); gPolyList(const gPolyList &); ~gPolyList(); // Deletes all pointees @@ -60,8 +59,7 @@ template class gPolyList { gPolyList SystemInNewCoordinates(const Gambit::SquareMatrix &) const; // Information - const gSpace *AmbientSpace() const { return Space; } - const term_order *TermOrder() const { return Order; } + const VariableSpace *AmbientSpace() const { return Space; } int Length() const { return List.size(); } int Dmnsn() const { return Space->Dmnsn(); } bool IsMultiaffine() const; diff --git a/src/solvers/enumpoly/gpolylst.imp b/src/solvers/enumpoly/gpolylst.imp index 06aef6527..eb39761e3 100644 --- a/src/solvers/enumpoly/gpolylst.imp +++ b/src/solvers/enumpoly/gpolylst.imp @@ -42,11 +42,8 @@ Gambit::List InteriorSegment(const Gambit::List &p_list, int first, int la // Constructors / Destructors //--------------------------- - template -gPolyList::gPolyList(const gSpace *sp, const term_order *to, - const Gambit::List *> &plist) - : Space(sp), Order(to), List() +gPolyList::gPolyList(const VariableSpace *sp, const Gambit::List *> &plist) : Space(sp) { for (int ii = 1; ii <= plist.size(); ii++) { auto *temp = new gPoly(*plist[ii]); @@ -55,8 +52,7 @@ gPolyList::gPolyList(const gSpace *sp, const term_order *to, } template -gPolyList::gPolyList(const gSpace *sp, const term_order *to, const Gambit::List> &list) - : Space(sp), Order(to), List() +gPolyList::gPolyList(const VariableSpace *sp, const Gambit::List> &list) : Space(sp) { for (int ii = 1; ii <= list.size(); ii++) { auto *temp = new gPoly(list[ii]); @@ -64,8 +60,7 @@ gPolyList::gPolyList(const gSpace *sp, const term_order *to, const Gambit::Li } } -template -gPolyList::gPolyList(const gPolyList &lst) : Space(lst.Space), Order(lst.Order), List() +template gPolyList::gPolyList(const gPolyList &lst) : Space(lst.Space) { for (int ii = 1; ii <= lst.List.size(); ii++) { auto *temp = new gPoly(*(lst.List[ii])); @@ -102,7 +97,7 @@ template gPolyList &gPolyList::operator=(const gPolyList &rhs template bool gPolyList::operator==(const gPolyList &rhs) const { - if (Space != rhs.Space || Order != rhs.Order) { + if (Space != rhs.Space) { return false; } if (List.size() != rhs.List.size()) { @@ -129,8 +124,6 @@ template void gPolyList::operator+=(const gPolyList &new_list) } } - - //------------------------------------------ // New Coordinate Systems //------------------------------------------ @@ -142,7 +135,7 @@ gPolyList gPolyList::TranslateOfSystem(const Gambit::Vector &new_origin for (int i = 1; i <= Length(); i++) { new_polys.push_back((*this)[i].TranslateOfPoly(new_origin)); } - return gPolyList(AmbientSpace(), TermOrder(), new_polys); + return gPolyList(AmbientSpace(), new_polys); } template @@ -152,7 +145,7 @@ gPolyList gPolyList::SystemInNewCoordinates(const Gambit::SquareMatrix for (int i = 1; i <= Length(); i++) { new_polys.push_back((*this)[i].PolyInNewCoordinates(M)); } - return gPolyList(AmbientSpace(), TermOrder(), new_polys); + return gPolyList(AmbientSpace(), new_polys); } //---------------------------------- diff --git a/src/solvers/enumpoly/ineqsolv.h b/src/solvers/enumpoly/ineqsolv.h index 5ce2f5bbc..328e2ac9b 100644 --- a/src/solvers/enumpoly/ineqsolv.h +++ b/src/solvers/enumpoly/ineqsolv.h @@ -75,8 +75,7 @@ template class IneqSolv { bool operator!=(const IneqSolv &) const; // Information - const gSpace *AmbientSpace() const { return System.AmbientSpace(); } - const term_order *TermOrder() const { return System.TermOrder(); } + const VariableSpace *AmbientSpace() const { return System.AmbientSpace(); } int Dmnsn() const { return System.Dmnsn(); } const gPolyList &UnderlyingEquations() const { return System; } T ErrorTolerance() const { return Epsilon; } diff --git a/src/solvers/enumpoly/monomial.cc b/src/solvers/enumpoly/monomial.cc deleted file mode 100644 index 38bae6b99..000000000 --- a/src/solvers/enumpoly/monomial.cc +++ /dev/null @@ -1,27 +0,0 @@ -// -// This file is part of Gambit -// Copyright (c) 1994-2024, The Gambit Project (http://www.gambit-project.org) -// -// FILE: src/tools/enumpoly/monomial.cc -// Instantiation of monomial classes -// -// This program is free software; you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation; either version 2 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -// - -#include "monomial.imp" - -template class gMono; -template class gMono; -// template class gMono; diff --git a/src/solvers/enumpoly/monomial.h b/src/solvers/enumpoly/monomial.h deleted file mode 100644 index d40dd9ede..000000000 --- a/src/solvers/enumpoly/monomial.h +++ /dev/null @@ -1,66 +0,0 @@ -// -// This file is part of Gambit -// Copyright (c) 1994-2024, The Gambit Project (http://www.gambit-project.org) -// -// FILE: src/tools/enumpoly/monomial.h -// Declaration of monomial 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 "prepoly.h" - -// This file provides the template class -// -// gMono -// -// whose objects are monomials in several variables -// with coefficients of class T and nonnegative exponents. -// This role of this class is to support the class gPoly. - -template class gMono { -private: - T coef; - exp_vect exps; - -public: - // constructors - gMono(const gSpace *, const T &); - gMono(const T &, const exp_vect &); - gMono(const gMono &); - ~gMono(); - - // operators - gMono &operator=(const gMono &); - - bool operator==(const gMono &) const; - bool operator!=(const gMono &) const; - gMono operator*(const gMono &) const; - gMono operator/(const gMono &) const; - gMono operator+(const gMono &) const; // assert exps == - gMono &operator+=(const gMono &); // assert exps == - gMono &operator*=(const T &); - gMono operator-() const; - - // information - const T &Coef() const; - int Dmnsn() const; - int TotalDegree() const; - bool IsConstant() const; - bool IsMultiaffine() const; - const exp_vect &ExpV() const; - T Evaluate(const Gambit::Array &) const; - T Evaluate(const Gambit::Vector &) const; -}; diff --git a/src/solvers/enumpoly/monomial.imp b/src/solvers/enumpoly/monomial.imp deleted file mode 100644 index 0214402e4..000000000 --- a/src/solvers/enumpoly/monomial.imp +++ /dev/null @@ -1,134 +0,0 @@ -// -// This file is part of Gambit -// Copyright (c) 1994-2024, The Gambit Project (http://www.gambit-project.org) -// -// FILE: src/tools/enumpoly/monomial.imp -// Implementation of monomial classes -// -// This program is free software; you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation; either version 2 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -// - -#include "monomial.h" - -//-------------------------------------------------------------------------- -// gMono -- constructors and destructor -//-------------------------------------------------------------------------- - -template gMono::gMono(const gSpace *p, const T &x) : coef(x), exps(p) {} - -template gMono::gMono(const T &x, const exp_vect &e) : coef(x), exps(e) -{ - if (x == (T)0) { - exps.ToZero(); - } -} - -template gMono::gMono(const gMono &y) : coef(y.coef), exps(y.exps) {} - -template gMono::~gMono() = default; - -//-------------------------------------------------------------------------- -// gMono -- operators -//-------------------------------------------------------------------------- - -template gMono &gMono::operator=(const gMono &y) -{ - if (this != &y) { - coef = y.coef; - exps = y.exps; - } - return *this; -} - -template bool gMono::operator==(const gMono &y) const -{ - return (coef == y.coef && exps == y.exps); -} - -template bool gMono::operator!=(const gMono &y) const { return !(*this == y); } - -template gMono gMono::operator*(const gMono &y) const -{ - return gMono(coef * y.coef, exps + y.exps); -} - -template gMono gMono::operator/(const gMono &y) const -{ - // assert ( y.coef != (T)0); - return gMono(coef / y.coef, exps - y.exps); -} - -template gMono gMono::operator+(const gMono &y) const -{ - // assert (exps == y.exps); - return gMono(coef + y.coef, exps); -} - -template gMono &gMono::operator+=(const gMono &y) -{ - // assert (exps == y.exps); - coef += y.coef; - return *this; -} - -template gMono &gMono::operator*=(const T &val) -{ - coef *= val; - return *this; -} - -template gMono gMono::operator-() const { return gMono(-coef, exps); } - -//-------------------------------------------------------------------------- -// gMono -- information -//-------------------------------------------------------------------------- - -template const T &gMono::Coef() const { return coef; } - -template int gMono::Dmnsn() const { return exps.Dmnsn(); } - -template int gMono::TotalDegree() const { return exps.TotalDegree(); } - -template const exp_vect &gMono::ExpV() const { return exps; } - -template bool gMono::IsConstant() const { return exps.IsConstant(); } - -template bool gMono::IsMultiaffine() const { return exps.IsMultiaffine(); } - -template T gMono::Evaluate(const Gambit::Array &vals) const -{ - T answer = Coef(); - - for (int i = 1; i <= Dmnsn(); i++) { - for (int j = 1; j <= exps[i]; j++) { - answer *= vals[i]; - } - } - - return answer; -} - -template T gMono::Evaluate(const Gambit::Vector &vals) const -{ - T answer = Coef(); - - for (int i = 1; i <= Dmnsn(); i++) { - for (int j = 1; j <= exps[i]; j++) { - answer *= vals[i]; - } - } - - return answer; -} diff --git a/src/solvers/enumpoly/nfgpoly.cc b/src/solvers/enumpoly/nfgpoly.cc index 8ff23a99a..3521f9dfd 100644 --- a/src/solvers/enumpoly/nfgpoly.cc +++ b/src/solvers/enumpoly/nfgpoly.cc @@ -37,18 +37,17 @@ namespace { // The polynomial representation of each strategy probability, substituting in // the sum-to-one equation for the probability of the last strategy for each player -std::map> BuildStrategyVariables(const gSpace &space, - const term_order &lex, +std::map> BuildStrategyVariables(const VariableSpace &space, const StrategySupportProfile &support) { int index = 1; std::map> strategy_poly; for (auto player : support.GetGame()->GetPlayers()) { auto strategies = support.GetStrategies(player); - gPoly residual(&space, 1, &lex); + gPoly residual(&space, 1); for (auto strategy : strategies) { if (strategy != strategies.back()) { - strategy_poly.try_emplace(strategy, &space, index, 1, &lex); + strategy_poly.try_emplace(strategy, &space, index, 1); residual -= strategy_poly.at(strategy); index++; } @@ -60,15 +59,15 @@ std::map> BuildStrategyVariables(const gSpace &space return strategy_poly; } -gPoly IndifferenceEquation(const gSpace &space, const term_order &lex, +gPoly IndifferenceEquation(const VariableSpace &space, const StrategySupportProfile &support, const std::map> &strategy_poly, const GameStrategy &s1, const GameStrategy &s2) { - gPoly equation(&space, &lex); + gPoly equation(&space); for (auto iter : StrategyContingencies(support, {s1})) { - gPoly term(&space, 1, &lex); + gPoly term(&space, 1); for (auto player : support.GetGame()->GetPlayers()) { if (player != s1->GetPlayer()) { term *= strategy_poly.at(iter->GetStrategy(player)); @@ -80,17 +79,17 @@ gPoly IndifferenceEquation(const gSpace &space, const term_order &lex, return equation; } -gPolyList ConstructEquations(const gSpace &space, const term_order &lex, +gPolyList ConstructEquations(const VariableSpace &space, const StrategySupportProfile &support, const std::map> &strategy_poly) { - gPolyList equations(&space, &lex); + gPolyList equations(&space); // Indifference equations between pairs of strategies for each player for (auto player : support.GetPlayers()) { auto strategies = support.GetStrategies(player); for (auto s1 = strategies.begin(), s2 = std::next(strategies.begin()); s2 != strategies.end(); ++s1, ++s2) { - equations += IndifferenceEquation(space, lex, support, strategy_poly, *s1, *s2); + equations += IndifferenceEquation(space, support, strategy_poly, *s1, *s2); } } // Inequalities for last probability for each player @@ -109,11 +108,10 @@ std::list> EnumPolyStrategySupportSolve(const StrategySupportProfile &support, bool &is_singular, int p_stopAfter) { - gSpace Space(support.MixedProfileLength() - support.GetGame()->NumPlayers()); - term_order Lex(&Space, lex); + VariableSpace Space(support.MixedProfileLength() - support.GetGame()->NumPlayers()); - auto strategy_poly = BuildStrategyVariables(Space, Lex, support); - gPolyList equations = ConstructEquations(Space, Lex, support, strategy_poly); + auto strategy_poly = BuildStrategyVariables(Space, support); + gPolyList equations = ConstructEquations(Space, support, strategy_poly); Vector bottoms(Space.Dmnsn()), tops(Space.Dmnsn()); bottoms = 0; diff --git a/src/solvers/enumpoly/poly.cc b/src/solvers/enumpoly/poly.cc deleted file mode 100644 index 5e1fb0840..000000000 --- a/src/solvers/enumpoly/poly.cc +++ /dev/null @@ -1,26 +0,0 @@ -// -// This file is part of Gambit -// Copyright (c) 1994-2024, The Gambit Project (http://www.gambit-project.org) -// -// FILE: src/tools/enumpoly/poly.cc -// Instantiation of polynomial classes -// -// This program is free software; you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation; either version 2 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -// - -#include "poly.imp" - -template class polynomial; -template class polynomial; diff --git a/src/solvers/enumpoly/poly.h b/src/solvers/enumpoly/poly.h deleted file mode 100644 index d6add239f..000000000 --- a/src/solvers/enumpoly/poly.h +++ /dev/null @@ -1,150 +0,0 @@ -// -// This file is part of Gambit -// Copyright (c) 1994-2024, The Gambit Project (http://www.gambit-project.org) -// -// FILE: src/tools/enumpoly/poly.h -// Declaration of polynomial classes -// -// This program is free software; you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation; either version 2 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -// - -#include "gambit.h" -#include "interval.h" -#include "gcomplex.h" - -/* This file supplies the template class - - polynomial - -These are univariate polynomials with coefficients of class T. -Polynomials are implemented as Gambit::List's of coefficients. There is no -attempt to maintain sparseness. - -*/ - -template class polynomial { - -private: - Gambit::List coeflist; - -public: - // constructors and destructor - explicit polynomial(int = -1); - polynomial(const polynomial &); - explicit polynomial(const Gambit::List &); - explicit polynomial(const Gambit::Vector &); - polynomial(const T &, const int &); - ~polynomial() = default; - - // unary operators - polynomial operator-() const; - polynomial Derivative() const; - - // binary operators - polynomial &operator=(const polynomial &y); - bool operator==(const polynomial &y) const; - bool operator!=(const polynomial &y) const; - const T &operator[](int index) const; - polynomial operator+(const polynomial &y) const; - polynomial operator-(const polynomial &y) const; - polynomial operator*(const polynomial &y) const; - polynomial operator/(const polynomial &y) const; - polynomial &operator+=(const polynomial &y); - polynomial &operator-=(const polynomial &y); - polynomial &operator*=(const polynomial &y); - polynomial &operator/=(const polynomial &y); - polynomial operator%(const polynomial &y) const; - - // manipulation - void ToMonic(); - // polynomial Togdouble() const; - - polynomial TogDouble() const; - - // information - bool IsZero() const; - T EvaluationAt(const T &arg) const; - int Degree() const; - T LeadingCoefficient() const; - Gambit::List CoefficientList() const; - polynomial GcdWith(const polynomial &) const; - bool IsQuadratfrei() const; - bool CannotHaveRootsIn(const gInterval &) const; - Gambit::List> RootSubintervals(const gInterval &) const; - gInterval NeighborhoodOfRoot(const gInterval &, T &) const; - Gambit::List> PreciseRootIntervals(const gInterval &, T &) const; - Gambit::List PreciseRoots(const gInterval &, T &) const; -}; - -/* REMARKS - - The function cannot_have_roots_in is based on the principle that if - -f = a_0 + a_1x + ... + a_dx^d - -with a_0 > 0, then - -abs(f(t)) >= a_0 - max{abs(a_1),...,abs(a_d)}*(abs(t) + ... + abs(t)^d) - -and the RHS will be positive whenever - -//WRONG! abs(t) < a_0/(a_0 + max{abs(a_1),...,abs(a_d)}). - -*/ - -class complexpoly { - -private: - Gambit::List coeflist; - -public: - // constructors and destructor - explicit complexpoly(int = -1); - complexpoly(const complexpoly &); - explicit complexpoly(const Gambit::List &); - complexpoly(const gComplex &, const int &); - ~complexpoly(); - - // unary operators - complexpoly operator-() const; - complexpoly Derivative() const; - - // binary operators - complexpoly &operator=(const complexpoly &y); - bool operator==(const complexpoly &y) const; - bool operator!=(const complexpoly &y) const; - const gComplex &operator[](int index) const; - complexpoly operator+(const complexpoly &y) const; - complexpoly operator-(const complexpoly &y) const; - complexpoly operator*(const complexpoly &y) const; - complexpoly operator/(const complexpoly &y) const; - complexpoly &operator+=(const complexpoly &y); - complexpoly &operator-=(const complexpoly &y); - complexpoly &operator*=(const complexpoly &y); - complexpoly &operator/=(const complexpoly &y); - complexpoly operator%(const complexpoly &y) const; - - // manipulation - void ToMonic(); - - // information - bool IsZero() const; - gComplex EvaluationAt(const gComplex &arg) const; - int Degree() const; - gComplex LeadingCoefficient() const; - complexpoly GcdWith(const complexpoly &) const; - bool IsQuadratfrei() const; - Gambit::List Roots() const; -}; diff --git a/src/solvers/enumpoly/poly.imp b/src/solvers/enumpoly/poly.imp deleted file mode 100644 index 3629965f6..000000000 --- a/src/solvers/enumpoly/poly.imp +++ /dev/null @@ -1,862 +0,0 @@ -// -// This file is part of Gambit -// Copyright (c) 1994-2024, The Gambit Project (http://www.gambit-project.org) -// -// FILE: src/tools/enumpoly/poly.imp -// Implementation of polynomial 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 -#include "poly.h" - -//-------------------------------------------------------------------------- -// class: polynomial -//-------------------------------------------------------------------------- - -//-------------------------------------------------------------------------- -// constructors and a destructor -//-------------------------------------------------------------------------- - -template polynomial::polynomial(const polynomial &x) : coeflist(x.coeflist) {} - -template polynomial::polynomial(const T &coeff, const int °) -{ - if (coeff != (T)0) { - for (int i = 0; i < deg; i++) { - coeflist.push_back((T)0); - } - coeflist.push_back(coeff); - } -} - -template -polynomial::polynomial(const Gambit::List &coefficientlist) : coeflist(coefficientlist) -{ -} - -template -polynomial::polynomial(const Gambit::Vector &coefficientvector) : coeflist() -{ - for (int i = 1; i <= coefficientvector.Length(); i++) { - coeflist.push_back(coefficientvector[i]); - } -} - -template polynomial::polynomial(int deg) : coeflist() -{ - if (deg >= 0) { - // gout << "Error is polynomial int constructor.\n"; - exit(1); - } -} - -//-------------------------------------------------------------------------- -// operators -//-------------------------------------------------------------------------- - -template polynomial &polynomial::operator=(const polynomial &y) -{ - if (this != &y) { - coeflist = y.coeflist; - } - - return *this; -} - -template bool polynomial::operator==(const polynomial &y) const -{ - if (Degree() != y.Degree()) { - return false; - } - else { - for (int i = 0; i <= Degree(); i++) { - if (coeflist[i + 1] != y.coeflist[i + 1]) { - return false; - } - } - } - return true; -} - -template bool polynomial::operator!=(const polynomial &y) const -{ - return !(*this == y); -} - -template const T &polynomial::operator[](const int index) const -{ - return coeflist[index + 1]; -} - -template polynomial polynomial::operator+(const polynomial &y) const -{ - if (Degree() < 0) { - return polynomial(y); - } - else if (y.Degree() < 0) { - return polynomial(*this); - } - - int max_degree; - - if (Degree() > y.Degree()) { - max_degree = Degree(); - } - else { - max_degree = y.Degree(); - } - - polynomial sum; - for (int i = 0; i <= max_degree; i++) { - sum.coeflist.push_back((T)0); - if (i <= Degree()) { - sum.coeflist[i + 1] += coeflist[i + 1]; - } - if (i <= y.Degree()) { - sum.coeflist[i + 1] += y.coeflist[i + 1]; - } - } - - while (!sum.coeflist.empty() && sum.coeflist.back() == (T)0) { - sum.coeflist.pop_back(); - } - - return sum; -} - -template polynomial polynomial::operator-(const polynomial &y) const -{ - return polynomial(*this + (-y)); -} - -template polynomial polynomial::operator*(const polynomial &y) const -{ - if (Degree() == -1) { - return polynomial(*this); - } - else if (y.Degree() == -1) { - return polynomial(y); - } - - int tot_degree = Degree() + y.Degree(); - - polynomial product; - for (int t = 0; t <= tot_degree; t++) { - product.coeflist.push_back((T)0); - } - for (int i = 0; i <= Degree(); i++) { - for (int j = 0; j <= y.Degree(); j++) { - product.coeflist[i + j + 1] += (*this)[i] * y[j]; - } - } - - return product; -} - -template polynomial polynomial::operator/(const polynomial &q) const -{ - // assert(q.Degree() >= 0); - - polynomial ans; - polynomial r = *this; - while (r.Degree() >= q.Degree()) { - polynomial x(r.LeadingCoefficient() / q.LeadingCoefficient(), r.Degree() - q.Degree()); - ans += x; - r -= q * x; - } - return polynomial(ans); -} - -template polynomial &polynomial::operator+=(const polynomial &y) -{ - return ((*this) = (*this) + y); -} - -template polynomial &polynomial::operator-=(const polynomial &y) -{ - return ((*this) = (*this) - y); -} - -template polynomial &polynomial::operator*=(const polynomial &y) -{ - return ((*this) = (*this) * y); -} - -template polynomial &polynomial::operator/=(const polynomial &y) -{ - return ((*this) = (*this) / y); -} - -template polynomial polynomial::operator%(const polynomial &q) const -{ - // assert (q.Degree() != -1); - - polynomial ans; - polynomial r = *this; - - while (r.Degree() >= q.Degree()) { - polynomial x(r.LeadingCoefficient() / q.LeadingCoefficient(), r.Degree() - q.Degree()); - ans += x; - r -= q * x; - } - return polynomial(r); -} - -template polynomial polynomial::operator-() const -{ - polynomial negation; - for (int i = 0; i <= Degree(); i++) { - negation.coeflist.push_back(-coeflist[i + 1]); - } - - return negation; -} - -template polynomial polynomial::Derivative() const -{ - if (Degree() <= 0) { - return polynomial(-1); - } - - polynomial derivative; - for (int i = 1; i <= Degree(); i++) { - derivative.coeflist.push_back((T)i * coeflist[i + 1]); - } - - return derivative; -} - -//-------------------------------------------------------------------------- -// manipulation -//-------------------------------------------------------------------------- - -template void polynomial::ToMonic() -{ - // assert (!IsZero()); - - T lc = LeadingCoefficient(); - for (int i = 1; i <= coeflist.size(); i++) { - coeflist[i] /= lc; - } -} - -template polynomial polynomial::TogDouble() const -{ - Gambit::List newcoefs; - for (const auto &coef : coeflist) { - newcoefs.push_back((double)coef); - } - return polynomial(newcoefs); -} - -//-------------------------------------------------------------------------- -// information -//-------------------------------------------------------------------------- - -template bool polynomial::IsZero() const { return coeflist.empty(); } - -template T polynomial::EvaluationAt(const T &arg) const -{ - T answer; - - if (IsZero()) { - answer = (T)0; - } - else { - answer = coeflist[Degree() + 1]; - for (int i = Degree(); i >= 1; i--) { - answer *= arg; - answer += coeflist[i]; - } - } - - return answer; -} - -template int polynomial::Degree() const { return coeflist.size() - 1; } - -template T polynomial::LeadingCoefficient() const -{ - if (Degree() < 0) { - return (T)0; - } - else { - return coeflist[Degree() + 1]; - } -} - -template Gambit::List polynomial::CoefficientList() const { return coeflist; } - -template polynomial polynomial::GcdWith(const polynomial &that) const -{ - // assert( !this->IsZero() && !that.IsZero() ); - - polynomial numerator(*this); - numerator.ToMonic(); - polynomial denominator(that); - denominator.ToMonic(); - polynomial remainder(numerator % denominator); - - while (!remainder.IsZero()) { - remainder.ToMonic(); - numerator = denominator; - denominator = remainder; - remainder = numerator % denominator; - } - - return denominator; -} - -template bool polynomial::IsQuadratfrei() const -{ - polynomial Df(Derivative()); - if (Df.Degree() <= 0) { - return true; - } - if (GcdWith(Df).Degree() <= 1) { - return true; - } - else { - return false; - } -} - -template bool polynomial::CannotHaveRootsIn(const gInterval &I) const -{ - // get rid of easy cases - if (Degree() == -1) { - return false; - } - else if (Degree() == 0) { - return true; - } - else if (EvaluationAt(I.LowerBound()) == (T)0) { - return false; - } - - // generate list of derivatives - Gambit::List> derivs; - derivs.push_back(Derivative()); - int i; - for (i = 2; i <= Degree(); i++) { - derivs.push_back(derivs[i - 1].Derivative()); - } - - T val = EvaluationAt(I.LowerBound()); - if (val < (T)0) { - val = -val; - } - - // find max |c_0/Degree()*c_i|^(1/i) - int max_index = 0; - T base_of_max_index = (T)0; - T relevant_factorial = (T)1; - for (i = 1; i <= Degree(); i++) { - relevant_factorial *= (T)i; - T ith_coeff = derivs[i].EvaluationAt(I.LowerBound()) / relevant_factorial; - if (ith_coeff < (T)0) { - ith_coeff = -ith_coeff; - } - - if (ith_coeff != (T)0) { - T base = val / ((T)Degree() * ith_coeff); - - if (base_of_max_index == (T)0) { - max_index = i; - base_of_max_index = base; - } - else if (pow((T)base, max_index) < pow((T)base_of_max_index, i)) { - max_index = i; - base_of_max_index = base; - } - } - } - // assert(base_of_max_index != (T)0); - - if ((T)pow((T)I.Length(), max_index) < (T)base_of_max_index) { - return true; - } - else { - return false; - } -} - -template -Gambit::List> polynomial::RootSubintervals(const gInterval &I) const -{ // assert ( Degree() >= 0 && IsQuadratfrei() ); - - polynomial Df = Derivative(); - - Gambit::List> answer; - - Gambit::List> to_be_processed; - to_be_processed.push_back(I); - while (!to_be_processed.empty()) { - gInterval in_process = to_be_processed.back(); - to_be_processed.pop_back(); - - if (EvaluationAt(in_process.LowerBound()) == (T)0) { - if (Df.CannotHaveRootsIn(in_process)) { - answer.push_back(in_process); - } - else { - to_be_processed.push_back(in_process.RightHalf()); - to_be_processed.push_back(in_process.LeftHalf()); - } - } - else if (EvaluationAt(in_process.UpperBound()) == (T)0) { - if (Df.CannotHaveRootsIn(in_process)) { - if (in_process.UpperBound() == I.UpperBound()) { - answer.push_back(in_process); - } - } - else { - to_be_processed.push_back(in_process.RightHalf()); - to_be_processed.push_back(in_process.LeftHalf()); - } - } - - else if (!CannotHaveRootsIn(in_process)) { - if (Df.CannotHaveRootsIn(in_process)) { - if ((EvaluationAt(in_process.LowerBound()) < (T)0 && - EvaluationAt(in_process.UpperBound()) > (T)0) || - (EvaluationAt(in_process.LowerBound()) > (T)0 && - EvaluationAt(in_process.UpperBound()) < (T)0)) { - answer.push_back(in_process); - } - } - else { - to_be_processed.push_back(in_process.RightHalf()); - to_be_processed.push_back(in_process.LeftHalf()); - } - } - } - - return answer; -} - -template -gInterval polynomial::NeighborhoodOfRoot(const gInterval &I, T &error) const -{ - Gambit::List> intrvls; - intrvls.push_back(I); - while (intrvls.back().Length() >= error) { - if (EvaluationAt(intrvls.back().LowerBound()) == (T)0) { - intrvls.push_back(gInterval(intrvls.back().LowerBound(), intrvls.back().LowerBound())); - } - - else if (EvaluationAt(intrvls.back().UpperBound()) == (T)0) { - intrvls.push_back(gInterval(intrvls.back().UpperBound(), intrvls.back().UpperBound())); - } - - else if ((EvaluationAt(intrvls.back().LowerBound()) >= (T)0 && - EvaluationAt(intrvls.back().Midpoint()) <= (T)0) || - (EvaluationAt(intrvls.back().LowerBound()) <= (T)0 && - EvaluationAt(intrvls.back().Midpoint()) >= (T)0)) { - intrvls.push_back(intrvls.back().LeftHalf()); - } - - else { - intrvls.push_back(intrvls.back().RightHalf()); - } - } - - return intrvls.back(); - - // It is, perhaps, possible to speed this up, at least for double's - // by introducing Newton's method. -} - -template -Gambit::List> polynomial::PreciseRootIntervals(const gInterval &I, - T &error) const -{ - Gambit::List> coarse = RootSubintervals(I); - Gambit::List> fine; - - for (int i = 1; i <= coarse.size(); i++) { - fine.push_back(NeighborhoodOfRoot(coarse[i], error)); - } - - return fine; -} - -template -Gambit::List polynomial::PreciseRoots(const gInterval &I, T &error) const -{ - Gambit::List roots; - - polynomial p(*this), factor(*this); - - while (p.Degree() > 0) { - - int depth = 1; - polynomial probe(p.Derivative()); - polynomial current_gcd(p.GcdWith(probe)); - while (current_gcd.Degree() > 0) { - depth++; - factor = current_gcd; - probe = probe.Derivative(); - current_gcd = current_gcd.GcdWith(probe); - } - - for (int i = 1; i <= depth; i++) { - p = p / factor; - } - Gambit::List> fine = factor.PreciseRootIntervals(I, error); - for (int j = 1; j <= fine.size(); j++) { - T approx = fine[j].LowerBound(); - for (int h = 1; h <= 2; h++) { - approx -= factor.EvaluationAt(approx) / - factor.Derivative().EvaluationAt(approx); // Newton's Method - } - roots.push_back(approx); - } - factor = p; - } - - return roots; -} - -//-------------------------------------------------------------------------- -// class: complexpoly -//-------------------------------------------------------------------------- - -//-------------------------------------------------------------------------- -// constructors and a destructor -//-------------------------------------------------------------------------- - -complexpoly::complexpoly(const complexpoly &x) - - = default; - -complexpoly::complexpoly(const gComplex &coeff, const int °) -{ - if (coeff != (gComplex)0) { - for (int i = 0; i < deg; i++) { - coeflist.push_back((gComplex)0); - } - coeflist.push_back(coeff); - } -} - -complexpoly::complexpoly(const Gambit::List &coefficientlist) : coeflist(coefficientlist) -{ -} - -complexpoly::complexpoly(const int deg) : coeflist() -{ - if (deg >= 0) { - // gout << "Error is complexpoly int constructor.\n"; - exit(1); - } -} - -complexpoly::~complexpoly() = default; - -//-------------------------------------------------------------------------- -// operators -//-------------------------------------------------------------------------- - -complexpoly &complexpoly::operator=(const complexpoly &y) -{ - if (this != &y) { - coeflist = y.coeflist; - } - - return *this; -} - -bool complexpoly::operator==(const complexpoly &y) const -{ - if (Degree() != y.Degree()) { - return false; - } - else { - for (int i = 0; i <= Degree(); i++) { - if (coeflist[i + 1] != y.coeflist[i + 1]) { - return false; - } - } - } - return true; -} - -bool complexpoly::operator!=(const complexpoly &y) const { return !(*this == y); } - -const gComplex &complexpoly::operator[](const int index) const { return coeflist[index + 1]; } - -complexpoly complexpoly::operator+(const complexpoly &y) const -{ - if (Degree() < 0) { - return {y}; - } - else if (y.Degree() < 0) { - return {*this}; - } - - int max_degree; - - if (Degree() > y.Degree()) { - max_degree = Degree(); - } - else { - max_degree = y.Degree(); - } - - complexpoly sum; - for (int i = 0; i <= max_degree; i++) { - sum.coeflist.push_back((gComplex)0); - if (i <= Degree()) { - sum.coeflist[i + 1] += coeflist[i + 1]; - } - if (i <= y.Degree()) { - sum.coeflist[i + 1] += y.coeflist[i + 1]; - } - } - - while (!sum.coeflist.empty() && sum.coeflist.back() == (gComplex)0) { - sum.coeflist.pop_back(); - } - - return sum; -} - -complexpoly complexpoly::operator-(const complexpoly &y) const -{ - return complexpoly(*this + (-y)); -} - -complexpoly complexpoly::operator*(const complexpoly &y) const -{ - if (Degree() == -1) { - return {*this}; - } - else if (y.Degree() == -1) { - return {y}; - } - - int tot_degree = Degree() + y.Degree(); - - complexpoly product; - for (int t = 0; t <= tot_degree; t++) { - product.coeflist.push_back((gComplex)0); - } - for (int i = 0; i <= Degree(); i++) { - for (int j = 0; j <= y.Degree(); j++) { - product.coeflist[i + j + 1] += (*this)[i] * y[j]; - } - } - - return product; -} - -complexpoly complexpoly::operator/(const complexpoly &q) const -{ - // assert(q.Degree() >= 0); - - complexpoly ans; - complexpoly r = *this; - while (r.Degree() >= q.Degree()) { - complexpoly x(r.LeadingCoefficient() / q.LeadingCoefficient(), r.Degree() - q.Degree()); - ans += x; - r -= q * x; - } - return {ans}; -} - -complexpoly &complexpoly::operator+=(const complexpoly &y) { return ((*this) = (*this) + y); } - -complexpoly &complexpoly::operator-=(const complexpoly &y) { return ((*this) = (*this) - y); } - -complexpoly &complexpoly::operator*=(const complexpoly &y) { return ((*this) = (*this) * y); } - -complexpoly &complexpoly::operator/=(const complexpoly &y) { return ((*this) = (*this) / y); } - -complexpoly complexpoly::operator%(const complexpoly &q) const -{ - // assert (q.Degree() != -1); - - complexpoly ans; - complexpoly r = *this; - - while (r.Degree() >= q.Degree()) { - complexpoly x(r.LeadingCoefficient() / q.LeadingCoefficient(), r.Degree() - q.Degree()); - ans += x; - r -= q * x; - } - return {r}; -} - -complexpoly complexpoly::operator-() const -{ - complexpoly negation; - for (int i = 0; i <= Degree(); i++) { - negation.coeflist.push_back(-coeflist[i + 1]); - } - - return negation; -} - -complexpoly complexpoly::Derivative() const -{ - if (Degree() <= 0) { - return complexpoly(-1); - } - - complexpoly derivative; - for (int i = 1; i <= Degree(); i++) { - derivative.coeflist.push_back((gComplex)i * coeflist[i + 1]); - } - - return derivative; -} - -//-------------------------------------------------------------------------- -// manipulation -//-------------------------------------------------------------------------- - -void complexpoly::ToMonic() -{ - // assert (!IsZero()); - - gComplex lc = LeadingCoefficient(); - for (int i = 1; i <= coeflist.size(); i++) { - coeflist[i] /= lc; - } -} - -//-------------------------------------------------------------------------- -// information -//-------------------------------------------------------------------------- - -bool complexpoly::IsZero() const { return coeflist.empty(); } - -gComplex complexpoly::EvaluationAt(const gComplex &arg) const -{ - gComplex answer; - - for (int i = 0; i <= Degree(); i++) { - auto monom_val = static_cast(1); - for (int j = 1; j <= i; j++) { - monom_val *= arg; - } - answer += coeflist[i + 1] * monom_val; - } - - return answer; -} - -int complexpoly::Degree() const { return coeflist.size() - 1; } - -gComplex complexpoly::LeadingCoefficient() const -{ - if (Degree() < 0) { - return (gComplex)0; - } - else { - return coeflist[Degree() + 1]; - } -} - -complexpoly complexpoly::GcdWith(const complexpoly &that) const -{ - // assert( !this->IsZero() && !that.IsZero() ); - - complexpoly numerator(*this); - numerator.ToMonic(); - complexpoly denominator(that); - denominator.ToMonic(); - complexpoly remainder(numerator % denominator); - - while (!remainder.IsZero()) { - remainder.ToMonic(); - numerator = denominator; - denominator = remainder; - remainder = numerator % denominator; - } - - return denominator; -} - -bool complexpoly::IsQuadratfrei() const -{ - complexpoly Df(Derivative()); - if (Df.Degree() <= 0) { - return true; - } - if (GcdWith(Df).Degree() <= 1) { - return true; - } - else { - return false; - } -} - -Gambit::List complexpoly::Roots() const -{ - // assert (!IsZero()); - - Gambit::List answer; - - if (Degree() == 0) { - return answer; - } - - complexpoly deriv(Derivative()); - - gComplex guess(1.3, 0.314159); - - while (fabs(EvaluationAt(guess)) > 0.00001) { - gComplex diff = EvaluationAt(guess) / deriv.EvaluationAt(guess); - int count = 0; - bool done = false; - while (!done) { - if (count < 10 && fabs(EvaluationAt(guess - diff)) >= fabs(EvaluationAt(guess))) { - diff /= gComplex(4.0, 0.0); - count++; - } - else { - done = true; - } - } - if (count == 10) { - // gout << "Failure in complexpoly::Roots().\n"; - exit(1); - } - - guess -= diff; - } - - answer.push_back(guess); - - Gambit::List lin_form_coeffs; - lin_form_coeffs.push_back(guess); - lin_form_coeffs.push_back(gComplex(-1.0, 0.0)); - complexpoly linear_form(lin_form_coeffs); - complexpoly quotient = *this / linear_form; - for (const auto &root : quotient.Roots()) { - answer.push_back(root); - } - - for (int i = 1; i <= answer.size(); i++) { // "Polish" each root twice - answer[i] -= EvaluationAt(answer[i]) / deriv.EvaluationAt(answer[i]); - answer[i] -= EvaluationAt(answer[i]) / deriv.EvaluationAt(answer[i]); - } - - return answer; -} diff --git a/src/solvers/enumpoly/prepoly.cc b/src/solvers/enumpoly/prepoly.cc deleted file mode 100644 index 69b25e580..000000000 --- a/src/solvers/enumpoly/prepoly.cc +++ /dev/null @@ -1,497 +0,0 @@ -// -// This file is part of Gambit -// Copyright (c) 1994-2024, The Gambit Project (http://www.gambit-project.org) -// -// FILE: src/tools/enumpoly/prepoly.cc -// Implementation of supporting classes for polynomials -// -// 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 "prepoly.h" - -//----------------------------------------------------------- -// gSpace -//----------------------------------------------------------- - -//------------------------- -// Constructors/Destructors -//------------------------- - -gSpace::gSpace(int nvars) : Variables() -{ - Variable *newvar; - for (int i = 1; i <= nvars; i++) { - newvar = new Variable; - newvar->Name = 'n'; - newvar->Name += Gambit::lexical_cast(i); - newvar->number = i; - Variables.push_back(newvar); - } -} - -gSpace::gSpace(const gSpace &p) : Variables() -{ - Variable *newvar; - for (int i = 1; i <= p.Variables.Length(); i++) { - newvar = new Variable; - newvar->Name = p.Variables[i]->Name; - newvar->number = i; - Variables.push_back(newvar); - } -} - -gSpace::~gSpace() -{ - for (int i = 1; i <= Variables.Length(); i++) { - delete Variables[i]; - } -} - -//----------------- -// Member Functions -//----------------- - -gSpace &gSpace::operator=(const gSpace &rhs) -{ - if (*this == rhs) { - return *this; - } - - Variables = rhs.Variables; - return *this; -} - -int gSpace::Dmnsn() const { return Variables.Length(); } - -Variable *gSpace::VariableWithNumber(int i) const { return Variables[i]; } - -Variable *gSpace::operator[](int i) const { return VariableWithNumber(i); } - -bool gSpace::operator==(const gSpace &rhs) const -{ - if (Variables.Length() == rhs.Variables.Length() && Variables == rhs.Variables) { - return true; - } - else { - return false; - } -} - -bool gSpace::operator!=(const gSpace &rhs) const { return !(*this == rhs); } - -//------------------------------------------------------ -// exp_vect -//------------------------------------------------------ - -//------------------------- -// Constructors/Destructors -//------------------------- - -exp_vect::exp_vect(const gSpace *p) : Space(p), components(p->Dmnsn()) -{ - for (int i = 1; i <= p->Dmnsn(); i++) { - components[i] = 0; - } -} - -exp_vect::exp_vect(const gSpace *p, const int &var, const int &exp) - : Space(p), components(p->Dmnsn()) -{ - for (int i = 1; i <= Dmnsn(); i++) { - components[i] = 0; - } - components[var] = exp; -} - -exp_vect::exp_vect(const gSpace *p, int *exponents) : Space(p), components(p->Dmnsn()) -{ - for (int i = 1; i <= Dmnsn(); i++) { - components[i] = exponents[i - 1]; - } -} - -exp_vect::exp_vect(const gSpace *p, Gambit::Vector exponents) - : Space(p), components(p->Dmnsn()) -{ - for (int i = 1; i <= Dmnsn(); i++) { - components[i] = exponents[i]; - } -} - -exp_vect::exp_vect(const gSpace *p, Gambit::Array exponents) - : Space(p), components(p->Dmnsn()) -{ - for (int i = 1; i <= Dmnsn(); i++) { - components[i] = exponents[i]; - } -} - -//------------------------- -// Operators -//------------------------- - -exp_vect &exp_vect::operator=(const exp_vect &RHS) -{ - if (this == &RHS) { - return *this; - } - - Space = RHS.Space; - components = RHS.components; - return *this; -} - -int exp_vect::operator[](int index) const { return components[index]; } - -bool exp_vect::operator==(const exp_vect &RHS) const -{ - if (components == RHS.components) { - return true; - } - else { - return false; - } -} - -bool exp_vect::operator!=(const exp_vect &RHS) const { return !(*this == RHS); } - -bool exp_vect::operator<=(const exp_vect &RHS) const -{ - for (int i = 1; i <= Dmnsn(); i++) { - if (components[i] > RHS.components[i]) { - return false; - } - } - - return true; -} - -bool exp_vect::operator>=(const exp_vect &RHS) const -{ - for (int i = 1; i <= Dmnsn(); i++) { - if (components[i] < RHS.components[i]) { - return false; - } - } - - return true; -} - -bool exp_vect::operator<(const exp_vect &RHS) const { return !(*this >= RHS); } - -bool exp_vect::operator>(const exp_vect &RHS) const { return !(*this <= RHS); } - -exp_vect exp_vect::operator-() const -{ - exp_vect tmp(Space); - for (int i = 1; i <= Dmnsn(); i++) { - tmp.components[i] = -components[i]; - } - - return tmp; -} - -exp_vect exp_vect::operator+(const exp_vect &credit) const -{ - // assert (Space == credit.Space); - - exp_vect tmp(Space); - for (int i = 1; i <= Dmnsn(); i++) { - tmp.components[i] = components[i] + credit.components[i]; - } - - return tmp; -} - -exp_vect exp_vect::operator-(const exp_vect &debit) const -{ - // assert (Space == debit.Space); - - exp_vect tmp(Space); - for (int i = 1; i <= Dmnsn(); i++) { - tmp.components[i] = components[i] - debit.components[i]; - } - - return tmp; -} - -void exp_vect::operator+=(const exp_vect &credit) -{ - - // assert (Space == credit.Space); - - for (int i = 1; i <= Dmnsn(); i++) { - components[i] += credit.components[i]; - } -} - -void exp_vect::operator-=(const exp_vect &debit) -{ - // assert (Space == debit.Space); - - for (int i = 1; i <= Dmnsn(); i++) { - components[i] -= debit.components[i]; - } -} - -//---------------------------- -// Other Operations -//---------------------------- - -exp_vect exp_vect::LCM(const exp_vect &arg2) const -{ - // assert (Space == arg2.Space); - - exp_vect tmp(Space); - for (int i = 1; i <= Dmnsn(); i++) { - if (components[i] < arg2.components[i]) { - tmp.components[i] = arg2.components[i]; - } - else { - tmp.components[i] = components[i]; - } - } - - return tmp; -} - -exp_vect exp_vect::AfterZeroingOutExpOfVariable(int &varnumber) const -{ - exp_vect tmp(*this); - tmp.components[varnumber] = 0; - return tmp; -} - -exp_vect exp_vect::AfterDecrementingExpOfVariable(int &varnumber) const -{ - exp_vect tmp(*this); - tmp.components[varnumber]--; - return tmp; -} - -//-------------------------- -// Information -//-------------------------- - -int exp_vect::Dmnsn() const { return Space->Dmnsn(); } - -bool exp_vect::IsConstant() const -{ - for (int i = 1; i <= Dmnsn(); i++) { - if ((*this)[i] > 0) { - return false; - } - } - return true; -} - -bool exp_vect::IsMultiaffine() const -{ - for (int i = 1; i <= Dmnsn(); i++) { - if ((*this)[i] > 1) { - return false; - } - } - return true; -} - -int exp_vect::TotalDegree() const -{ - int exp_sum = 0; - for (int i = 1; i <= Dmnsn(); i++) { - exp_sum += (*this)[i]; - } - return exp_sum; -} - -//-------------------------- -// Manipulation -//-------------------------- - -void exp_vect::ToZero() -{ - for (int i = 1; i <= Dmnsn(); i++) { - components[i] = 0; - } -} - -//------------------------------------------------------ -// term_order -//------------------------------------------------------ - -//------------------- -// Possible Orderings -//------------------- - -bool lex(const exp_vect &LHS, const exp_vect &RHS) -{ - for (int i = 1; i <= LHS.Dmnsn(); i++) { - if (LHS[i] < RHS[i]) { - return true; - } - else if (LHS[i] > RHS[i]) { - return false; - } - } - return false; -} - -bool reverselex(const exp_vect &LHS, const exp_vect &RHS) -{ - for (int i = LHS.Dmnsn(); i >= 1; i--) { - if (LHS[i] < RHS[i]) { - return true; - } - else if (LHS[i] > RHS[i]) { - return false; - } - } - return false; -} - -bool deglex(const exp_vect &LHS, const exp_vect &RHS) -{ - if (LHS.TotalDegree() < RHS.TotalDegree()) { - return true; - } - else if (LHS.TotalDegree() > RHS.TotalDegree()) { - return false; - } - - for (int i = 1; i <= LHS.Dmnsn(); i++) { - if (LHS[i] < RHS[i]) { - return true; - } - else if (LHS[i] > RHS[i]) { - return false; - } - } - return false; -} - -bool reversedeglex(const exp_vect &LHS, const exp_vect &RHS) -{ - if (LHS.TotalDegree() < RHS.TotalDegree()) { - return true; - } - else if (LHS.TotalDegree() > RHS.TotalDegree()) { - return false; - } - - for (int i = LHS.Dmnsn(); i >= 1; i--) { - if (LHS[i] < RHS[i]) { - return true; - } - else if (LHS[i] > RHS[i]) { - return false; - } - } - return false; -} - -bool degrevlex(const exp_vect &LHS, const exp_vect &RHS) -{ - if (LHS.TotalDegree() < RHS.TotalDegree()) { - return true; - } - else if (LHS.TotalDegree() > RHS.TotalDegree()) { - return false; - } - - for (int i = LHS.Dmnsn(); i >= 1; i--) { - if (LHS[i] < RHS[i]) { - return false; - } - else if (LHS[i] > RHS[i]) { - return true; - } - } - return false; -} - -bool reversedegrevlex(const exp_vect &LHS, const exp_vect &RHS) -{ - if (LHS.TotalDegree() < RHS.TotalDegree()) { - return true; - } - else if (LHS.TotalDegree() > RHS.TotalDegree()) { - return false; - } - - for (int i = 1; i <= LHS.Dmnsn(); i++) { - if (LHS[i] < RHS[i]) { - return false; - } - else if (LHS[i] > RHS[i]) { - return true; - } - } - return false; -} - -//------------------------- -// Constructors/Destructors -//------------------------- - -term_order::term_order(const gSpace *p, ORD_PTR act_ord) : Space(p), actual_order(act_ord) {} - -//------------------------- -// Operators -//------------------------- - -term_order &term_order::operator=(const term_order &RHS) -{ - if (this == &RHS) { - return *this; - } - - Space = RHS.Space; - actual_order = RHS.actual_order; - return *this; -} - -bool term_order::operator==(const term_order &RHS) const -{ - return (Space == RHS.Space && actual_order == RHS.actual_order); -} - -bool term_order::operator!=(const term_order &RHS) const { return !(*this == RHS); } - -//------------------------- -// Comparisons -//------------------------- - -bool term_order::Less(const exp_vect &LHS, const exp_vect &RHS) const -{ - return (*actual_order)(LHS, RHS); -} - -bool term_order::LessOrEqual(const exp_vect &LHS, const exp_vect &RHS) const -{ - return ((*actual_order)(LHS, RHS) || LHS == RHS); -} - -bool term_order::Greater(const exp_vect &LHS, const exp_vect &RHS) const -{ - return !(LessOrEqual(LHS, RHS)); -} - -bool term_order::GreaterOrEqual(const exp_vect &LHS, const exp_vect &RHS) const -{ - return !(Less(LHS, RHS)); -} diff --git a/src/solvers/enumpoly/prepoly.h b/src/solvers/enumpoly/prepoly.h deleted file mode 100644 index 18be0074a..000000000 --- a/src/solvers/enumpoly/prepoly.h +++ /dev/null @@ -1,182 +0,0 @@ -// -// This file is part of Gambit -// Copyright (c) 1994-2024, The Gambit Project (http://www.gambit-project.org) -// -// FILE: src/tools/enumpoly/prepoly.h -// Declaration of supporting classes for polynomials -// -// 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 PREPOLY_H -#define PREPOLY_H - -#include -#include "gambit.h" - -/* - The classes in this file are prior to the notion of a -multivariate polynomial. First of all, one needs a space which the -polynomials refer to. This is given by the notion of a gSpace. -A polynomial is a sum of monomials, where each monomial is a -coefficient multiplied by a power product, and each power product is -the vector of variables "raised" by an exponent vector. The exponent -vectors play a special role in many algorithms, especially those -associated with Groebner bases, and they have a class. Finally, the -notion of a term order on the set of exponent vector, which -encompasses potentially infinitely many distinct orders, seems best -implemented through a class that delivers a full menu of services -built on top of a pointer to a function for computing an order. -*/ - -// ************************* -// gSpace declaration -// ************************* - -struct Variable { - std::string Name; - int number; -}; - -class gSpace { -private: - Gambit::Array Variables; - -public: - explicit gSpace(int nvars = 0); - gSpace(const gSpace &); - ~gSpace(); - - // operators - gSpace &operator=(const gSpace &rhs); - - Variable *operator[](int) const; - bool operator==(const gSpace &rhs) const; - bool operator!=(const gSpace &rhs) const; - - // information - int Dmnsn() const; - Variable *VariableWithNumber(int) const; -}; - -// *********************** -// class exp_vect -// *********************** - -/* - Exponent vectors are vectors of integers. In specifying operators - we take the view that addition, subtraction, and order are defined, - but not inner or scalar products. -*/ - -class exp_vect { - -private: - const gSpace *Space; - Gambit::Vector components; - -public: - explicit exp_vect(const gSpace *); - exp_vect(const gSpace *, const int &, const int &); // x_i^j - exp_vect(const gSpace *, int *); - exp_vect(const gSpace *, Gambit::Vector); - exp_vect(const gSpace *, Gambit::Array); - exp_vect(const exp_vect &) = default; - ~exp_vect() = default; - - // Operators - exp_vect &operator=(const exp_vect &RHS); - - int operator[](int index) const; - - bool operator==(const exp_vect &RHS) const; - bool operator!=(const exp_vect &RHS) const; - bool operator<=(const exp_vect &RHS) const; - bool operator>=(const exp_vect &RHS) const; - bool operator<(const exp_vect &RHS) const; - bool operator>(const exp_vect &RHS) const; - - exp_vect operator-() const; - exp_vect operator+(const exp_vect &) const; - exp_vect operator-(const exp_vect &) const; - void operator+=(const exp_vect &); - void operator-=(const exp_vect &); - - // Other operations - exp_vect LCM(const exp_vect &) const; - exp_vect AfterZeroingOutExpOfVariable(int &) const; - exp_vect AfterDecrementingExpOfVariable(int &) const; - - // Information - int Dmnsn() const; - bool IsConstant() const; - bool IsMultiaffine() const; - int TotalDegree() const; - - // Manipulation - void ToZero(); -}; - -// *********************** -// class term_order -// *********************** - -/* - A term order is a total order of the set of exponent vectors -associated with a particular variable list, that has the properties: - - a) 1 < alpha for all alpha \ne 1; - b) if alpha < beta, then alpha + gamma < beta + gamma for all gamma >= 0. - - In our implementation we take the view that the order itself is a -variable of an object of the class, and implement this in terms of -pointers to functions. -*/ - -// THE FOLLOWING FUNCTIONS SHOULD BE VIEWED AS PRIVATE MEMBERS OF -// class term_order I WAS BAFFLED AS TO HOW TO HAVE A MEMBER THAT -// IS A POINTER-TO-OTHER-MEMBER-FUNCTION -typedef bool (*ORD_PTR)(const exp_vect &, const exp_vect &); -bool lex(const exp_vect &, const exp_vect &); -bool reverselex(const exp_vect &, const exp_vect &); -bool deglex(const exp_vect &, const exp_vect &); -bool reversedeglex(const exp_vect &, const exp_vect &); -bool degrevlex(const exp_vect &, const exp_vect &); -bool reversedegrevlex(const exp_vect &, const exp_vect &); - -class term_order { -private: - const gSpace *Space; - ORD_PTR actual_order; - -public: - term_order(const gSpace *, ORD_PTR); - term_order(const term_order &) = default; - ~term_order() = default; - - // Operators - term_order &operator=(const term_order &RHS); - - bool operator==(const term_order &RHS) const; - bool operator!=(const term_order &RHS) const; - - // Comparisons invoking the underlying order - bool Less(const exp_vect &, const exp_vect &) const; - bool LessOrEqual(const exp_vect &, const exp_vect &) const; - bool Greater(const exp_vect &, const exp_vect &) const; - bool GreaterOrEqual(const exp_vect &, const exp_vect &) const; -}; - -#endif // PREPOLY_H diff --git a/src/solvers/enumpoly/quiksolv.h b/src/solvers/enumpoly/quiksolv.h index 292f00340..a6ec4515a 100644 --- a/src/solvers/enumpoly/quiksolv.h +++ b/src/solvers/enumpoly/quiksolv.h @@ -123,8 +123,7 @@ template class QuikSolv { bool operator!=(const QuikSolv &) const; // Information - inline const gSpace *AmbientSpace() const { return System.AmbientSpace(); } - inline const term_order *TermOrder() const { return System.TermOrder(); } + inline const VariableSpace *AmbientSpace() const { return System.AmbientSpace(); } inline int Dmnsn() const { return System.Dmnsn(); } inline const gPolyList &UnderlyingEquations() const { return System; } inline bool WasSolved() const { return HasBeenSolved; } diff --git a/src/solvers/enumpoly/quiksolv.imp b/src/solvers/enumpoly/quiksolv.imp index 8154757d2..5791ba52c 100644 --- a/src/solvers/enumpoly/quiksolv.imp +++ b/src/solvers/enumpoly/quiksolv.imp @@ -34,7 +34,7 @@ using namespace Gambit; template QuikSolv::QuikSolv(const gPolyList &given) - : System(given), gDoubleSystem(given.AmbientSpace(), given.TermOrder(), given.NormalizedList()), + : System(given), gDoubleSystem(given.AmbientSpace(), given.NormalizedList()), NoEquations(std::min(System.Dmnsn(), System.Length())), NoInequalities(std::max(System.Length() - System.Dmnsn(), 0)), TreesOfPartials(gDoubleSystem), HasBeenSolved(false), Roots(), isMultiaffine(System.IsMultiaffine()), @@ -226,7 +226,6 @@ bool QuikSolv::NewtonRootInRectangle(const gRectangle &r, return false; } - //------------------------------------ // Is the Newton root the only root? //------------------------------------ @@ -388,7 +387,6 @@ Gambit::Vector QuikSolv::SlowNewtonPolishOnce(const Gambit::Vector::FindCertainNumberOfRoots(const gRectangle &r, const int &ma return false; } - -template void -QuikSolv::FindRootsRecursion(Gambit::List> *rootlistptr, - const gRectangle &r, const int &max_iterations, - Gambit::Array &precedence, int &iterations, int depth, - const int &max_no_roots, int *roots_found) const +template +void QuikSolv::FindRootsRecursion(Gambit::List> *rootlistptr, + const gRectangle &r, const int &max_iterations, + Gambit::Array &precedence, int &iterations, int depth, + const int &max_no_roots, int *roots_found) const { // // TLT: In some cases, this recursive process apparently goes into an From 77e73de443ea7097c338163659561a6414153c7f Mon Sep 17 00:00:00 2001 From: Ted Turocy Date: Fri, 13 Dec 2024 10:38:40 +0000 Subject: [PATCH 43/99] Simplify and organise implementation of gPoly --- src/solvers/enumpoly/efgpoly.cc | 2 +- src/solvers/enumpoly/gpoly.cc | 6 - src/solvers/enumpoly/gpoly.h | 194 ++++++++++------ src/solvers/enumpoly/gpoly.imp | 356 +++--------------------------- src/solvers/enumpoly/gpolylst.imp | 2 +- 5 files changed, 156 insertions(+), 404 deletions(-) diff --git a/src/solvers/enumpoly/efgpoly.cc b/src/solvers/enumpoly/efgpoly.cc index c4bf5200d..dada00e73 100644 --- a/src/solvers/enumpoly/efgpoly.cc +++ b/src/solvers/enumpoly/efgpoly.cc @@ -77,7 +77,7 @@ gPoly BuildSequenceVariable(ProblemData &p_data, const GameSequence &p_s } if (int constraint_coef = p_data.sfg.GetConstraintEntry(p_sequence->GetInfoset(), seq->action)) { - equation += double(constraint_coef) * BuildSequenceVariable(p_data, seq, var); + equation += BuildSequenceVariable(p_data, seq, var) * double(constraint_coef); } } return equation; diff --git a/src/solvers/enumpoly/gpoly.cc b/src/solvers/enumpoly/gpoly.cc index e7a8374e1..2cfe96f35 100644 --- a/src/solvers/enumpoly/gpoly.cc +++ b/src/solvers/enumpoly/gpoly.cc @@ -24,9 +24,3 @@ #include "gambit.h" template class gPoly; -template gPoly operator+(const gPoly &poly, const double &val); -template gPoly operator*(const double &val, const gPoly &poly); -template gPoly operator*(const gPoly &poly, const double &val); - -template gPoly ToDouble(const gPoly &); -template gPoly NormalizationOfPoly(const gPoly &); diff --git a/src/solvers/enumpoly/gpoly.h b/src/solvers/enumpoly/gpoly.h index 28dd2fab7..34f4479ea 100644 --- a/src/solvers/enumpoly/gpoly.h +++ b/src/solvers/enumpoly/gpoly.h @@ -23,6 +23,7 @@ #ifndef GPOLY_H #define GPOLY_H +#include #include "gambit.h" #include "core/sqmatrix.h" @@ -223,106 +224,153 @@ template class gMono { } }; -// These classes are used to store and mathematically manipulate polynomials. +// A multivariate polynomial template class gPoly { - private: - const VariableSpace *Space; // pointer to variable Space of space - Gambit::List> Terms; // alternative implementation - - //---------------------- - // some private members - //---------------------- + const VariableSpace *Space; + List> Terms; // Arithmetic - Gambit::List> Adder(const Gambit::List> &, - const Gambit::List> &) const; - Gambit::List> Mult(const Gambit::List> &, - const Gambit::List> &) const; + List> Adder(const List> &, const List> &) const; + List> Mult(const List> &, const List> &) const; gPoly DivideByPolynomial(const gPoly &den) const; - // The following is used to construct the translate of *this. - gPoly TranslateOfMono(const gMono &, const Gambit::Vector &) const; - gPoly MonoInNewCoordinates(const gMono &, const Gambit::SquareMatrix &) const; + gPoly TranslateOfMono(const gMono &, const Vector &) const; + gPoly MonoInNewCoordinates(const gMono &, const SquareMatrix &) const; public: - //--------------------------- - // Construction, destruction: - //--------------------------- - - // Null gPoly constructor - gPoly(const VariableSpace *); + gPoly(const VariableSpace *p) : Space(p) {} // Constructs a constant gPoly - gPoly(const VariableSpace *, const T &); - // Constructs a gPoly equal to another; - gPoly(const gPoly &); + gPoly(const VariableSpace *p, const T &constant) : Space(p) + { + if (constant != static_cast(0)) { + Terms.push_back(gMono(p, constant)); + } + } + gPoly(const gPoly &) = default; // Constructs a gPoly that is x_{var_no}^exp; - gPoly(const VariableSpace *p, int var_no, int exp); + gPoly(const VariableSpace *p, const int var_no, const int exp) : Space(p) + { + Terms.push_back(gMono(static_cast(1), ExponentVector(p, var_no, exp))); + } // Constructs a gPoly that is the monomial coeff*vars^exps; - gPoly(const VariableSpace *p, ExponentVector exps, T coeff); + gPoly(const VariableSpace *p, const ExponentVector &exps, const T &coeff) : Space(p) + { + Terms.push_back(gMono(coeff, exps)); + } // Constructs a gPoly with single monomial - gPoly(const VariableSpace *p, const gMono &); - + gPoly(const VariableSpace *p, const gMono &mono) : Space(p) { Terms.push_back(mono); } ~gPoly() = default; //---------- // Operators: //---------- - gPoly &operator=(const gPoly &); - // Set polynomial equal to the SOP form in the string - gPoly operator-() const; - gPoly operator-(const gPoly &) const; - void operator-=(const gPoly &); - gPoly operator+(const gPoly &) const; - void operator+=(const gPoly &); - void operator+=(const T &); - gPoly operator*(const gPoly &) const; - void operator*=(const gPoly &); - void operator*=(const T &); - gPoly operator/(const T &val) const; // division by a constant - gPoly operator/(const gPoly &) const; // division by a polynomial + gPoly &operator=(const gPoly &) = default; + gPoly operator-() const + { + gPoly neg(*this); + for (int j = 1; j <= Terms.size(); j++) { + neg.Terms[j] = -Terms[j]; + } + return neg; + } + gPoly operator-(const gPoly &p) const + { + gPoly dif(*this); + dif -= p; + return dif; + } + void operator-=(const gPoly &p) + { + gPoly neg = p; + for (int i = 1; i <= neg.Terms.size(); i++) { + neg.Terms[i] = -neg.Terms[i]; + } + Terms = Adder(Terms, neg.Terms); + } + gPoly operator+(const gPoly &p) const + { + gPoly sum(*this); + sum += p; + return sum; + } + gPoly operator+(const T &v) const + { + gPoly result(*this); + result += v; + return result; + } + void operator+=(const gPoly &p) { Terms = Adder(Terms, p.Terms); } + void operator+=(const T &val) { *this += gPoly(Space, val); } + gPoly operator*(const gPoly &p) const + { + gPoly prod(*this); + prod *= p; + return prod; + } + gPoly operator*(const T &v) const + { + gPoly result(*this); + result *= v; + return result; + } + void operator*=(const gPoly &p) { Terms = Mult(Terms, p.Terms); } + void operator*=(const T &val) + { + for (int j = 1; j <= Terms.size(); j++) { + Terms[j] *= val; + } + } + gPoly operator/(const T &val) const + { + if (val == static_cast(0)) { + throw ZeroDivideException(); + } + return (*this) * (static_cast(1) / val); + } + gPoly operator/(const gPoly &den) const { return DivideByPolynomial(den); } - bool operator==(const gPoly &p) const; - bool operator!=(const gPoly &p) const; + bool operator==(const gPoly &p) const { return Space == p.Space && Terms == p.Terms; } + bool operator!=(const gPoly &p) const { return Space != p.Space || Terms != p.Terms; } //------------- - // Information: + // Information //------------- - const VariableSpace *GetSpace() const; - int Dmnsn() const; - int DegreeOfVar(int var_no) const; - int Degree() const; - T GetCoef(const Gambit::Vector &Powers) const; - T GetCoef(const ExponentVector &Powers) const; + const VariableSpace *GetSpace() const { return Space; } + int Dmnsn() const { return Space->Dmnsn(); } + int DegreeOfVar(int var_no) const + { + return std::accumulate(Terms.begin(), Terms.end(), 0, [&var_no](int v, const gMono &m) { + return std::max(v, m.ExpV()[var_no]); + }); + } + int Degree() const + { + return std::accumulate(Terms.begin(), Terms.end(), 0, + [](int v, const gMono &m) { return std::max(v, m.TotalDegree()); }); + } gPoly LeadingCoefficient(int varnumber) const; - T NumLeadCoeff() const; // deg == 0 - bool IsConstant() const; - bool IsMultiaffine() const; - // assumes UniqueActiveVariable() is true - T Evaluate(const Gambit::Vector &values) const; + T NumLeadCoeff() const { return (Terms.size() == 1) ? Terms.front().Coef() : static_cast(0); } + bool IsMultiaffine() const + { + return std::all_of(Terms.begin(), Terms.end(), + [](const gMono &t) { return t.IsMultiaffine(); }); + } + T Evaluate(const Vector &values) const + { + return std::accumulate( + Terms.begin(), Terms.end(), static_cast(0), + [&values](const T &v, const gMono &m) { return v + m.Evaluate(values); }); + } gPoly PartialDerivative(int varnumber) const; - Gambit::List> MonomialList() const; + const List> &MonomialList() const { return Terms; } - gPoly TranslateOfPoly(const Gambit::Vector &) const; - gPoly PolyInNewCoordinates(const Gambit::SquareMatrix &) const; + gPoly TranslateOfPoly(const Vector &) const; + gPoly PolyInNewCoordinates(const SquareMatrix &) const; T MaximalValueOfNonlinearPart(const T &) const; + gPoly Normalize() const; }; -//------------- -// Conversion: -//------------- - -template gPoly ToDouble(const gPoly &); -template gPoly NormalizationOfPoly(const gPoly &); - -// global multiply by scalar operators -template gPoly operator*(const T &val, const gPoly &poly); -template gPoly operator*(const gPoly &poly, const T &val); - -// global add to scalar operators -template gPoly operator+(const T &val, const gPoly &poly); -template gPoly operator+(const gPoly &poly, const T &val); - #endif // # GPOLY_H diff --git a/src/solvers/enumpoly/gpoly.imp b/src/solvers/enumpoly/gpoly.imp index ebe531faf..c4010ab55 100644 --- a/src/solvers/enumpoly/gpoly.imp +++ b/src/solvers/enumpoly/gpoly.imp @@ -20,7 +20,7 @@ // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // -#include // for std::max() +#include #include "gpoly.h" #include "gambit.h" @@ -28,239 +28,12 @@ // gPoly //--------------------------------------------------------------- -//--------------------------- -// Constructors / Destructor -//--------------------------- - -template gPoly::gPoly(const VariableSpace *p) : Space(p) {} - -template gPoly::gPoly(const VariableSpace *p, const T &constant) : Space(p) -{ - if (constant != (T)0) { - Terms.push_back(gMono(p, constant)); - } -} - -template gPoly::gPoly(const VariableSpace *p, int var_no, int exp) : Space(p) -{ - Terms.push_back(gMono((T)1, ExponentVector(p, var_no, exp))); -} - -template gPoly::gPoly(const VariableSpace *p, ExponentVector exps, T coeff) : Space(p) -{ - Terms.push_back(gMono(coeff, exps)); -} - -template gPoly::gPoly(const VariableSpace *p, const gMono &mono) : Space(p) -{ - Terms.push_back(mono); -} - -template gPoly::gPoly(const gPoly &p) : Space(p.Space), Terms(p.Terms) -{ - *this = p; -} - -//---------------------------------- -// Operators -//---------------------------------- - -template gPoly &gPoly::operator=(const gPoly &p) -{ - // assert (Space == p.Space && Order == p.Order); - - Terms = p.Terms; - return (*this); -} - -template gPoly gPoly::operator-() const -{ - gPoly neg(*this); - for (int j = 1; j <= Terms.size(); j++) { - neg.Terms[j] = -Terms[j]; - } - return neg; -} - -template gPoly gPoly::operator-(const gPoly &p) const -{ - gPoly dif(*this); - dif -= p; - return dif; -} - -template void gPoly::operator-=(const gPoly &p) -{ - // assert(Space == p.Space); - - gPoly neg = p; - for (int i = 1; i <= neg.Terms.size(); i++) { - neg.Terms[i] = -neg.Terms[i]; - } - Terms = Adder(Terms, neg.Terms); -} - -template gPoly gPoly::operator+(const gPoly &p) const -{ - gPoly sum(*this); - sum += p; - return sum; -} - -template void gPoly::operator+=(const gPoly &p) -{ - // assert(Space == p.Space); - - Terms = Adder(Terms, p.Terms); -} - -template void gPoly::operator+=(const T &val) { *this += gPoly(Space, val); } - -template gPoly gPoly::operator*(const gPoly &p) const -{ - gPoly prod(*this); - prod *= p; - return prod; -} - -template gPoly gPoly::operator/(const T &val) const -{ - if (val == (T)0) { - throw Gambit::ZeroDivideException(); - } - T one = (T)1; - return (one / val) * (*this); -} - -template gPoly gPoly::operator/(const gPoly &den) const -{ - return DivideByPolynomial(den); -} - -template void gPoly::operator*=(const gPoly &p) -{ - // assert(Space == p.Space); - - Terms = Mult(Terms, p.Terms); -} - -template void gPoly::operator*=(const T &val) -{ - for (int j = 1; j <= Terms.size(); j++) { - Terms[j] *= val; - } -} - -template bool gPoly::operator==(const gPoly &p) const -{ - if (Terms.size() != p.Terms.size()) { - return false; - } - if (Terms.size() == 0 && p.Terms.size() == 0) { - return true; - } - - return (Terms == p.Terms); -} - -template bool gPoly::operator!=(const gPoly &p) const { return !(*this == p); } - -//---------------------------------- -// Information -//---------------------------------- - -template const VariableSpace *gPoly::GetSpace() const -{ - return (VariableSpace *)Space; -} - -template int gPoly::Dmnsn() const { return Space->Dmnsn(); } - -template int gPoly::DegreeOfVar(int var_no) const -{ - int max = 0; - for (int j = 1; j <= Terms.size(); j++) { - if (max < Terms[j].ExpV()[var_no]) { - max = Terms[j].ExpV()[var_no]; - } - } - return max; -} - -template int gPoly::Degree() const -{ - int max = 0; - for (int j = 1; j <= Terms.size(); j++) { - if (Terms[j].TotalDegree() > max) { - max = Terms[j].TotalDegree(); - } - } - return max; -} - -template T gPoly::GetCoef(const Gambit::Vector &Powers) const -{ - return GetCoef(ExponentVector(Space, Powers)); -} - -template T gPoly::GetCoef(const ExponentVector &Powers) const -{ - for (int j = 1; j <= Terms.size(); j++) { - if (Terms[j].ExpV() == Powers) { - return Terms[j].Coef(); - } - } - return (T)0; -} - -template T gPoly::NumLeadCoeff() const -{ - if (Terms.size() == 1) { - return Terms.front().Coef(); - } - else { - return (T)0; - } -} - -template bool gPoly::IsConstant() const -{ - for (const auto &term : Terms) { - if (!term.IsConstant()) { - return false; - } - } - return true; -} - -template bool gPoly::IsMultiaffine() const -{ - for (const auto &term : Terms) { - if (!term.IsMultiaffine()) { - return false; - } - } - return true; -} - -template T gPoly::Evaluate(const Gambit::Vector &values) const -{ - T answer = 0; - for (const auto &term : Terms) { - answer += term.Evaluate(values); - } - return answer; -} - -template Gambit::List> gPoly::MonomialList() const { return Terms; } - //------------------------------------------------------------- // Private Versions of Arithmetic Operators //------------------------------------------------------------- template -Gambit::List> gPoly::Adder(const Gambit::List> &One, - const Gambit::List> &Two) const +List> gPoly::Adder(const List> &One, const List> &Two) const { if (One.empty()) { return Two; @@ -269,7 +42,7 @@ Gambit::List> gPoly::Adder(const Gambit::List> &One, return One; } - Gambit::List> answer; + List> answer; int i = 1; int j = 1; @@ -305,17 +78,15 @@ Gambit::List> gPoly::Adder(const Gambit::List> &One, } template -Gambit::List> gPoly::Mult(const Gambit::List> &One, - const Gambit::List> &Two) const +List> gPoly::Mult(const List> &One, const List> &Two) const { - Gambit::List> answer; + List> answer; if (One.empty() || Two.empty()) { return answer; } - int i; - for (i = 1; i <= One.size(); i++) { + for (int i = 1; i <= One.size(); i++) { for (int j = 1; j <= Two.size(); j++) { gMono next = One[i] * Two[j]; @@ -368,10 +139,10 @@ Gambit::List> gPoly::Mult(const Gambit::List> &One, template gPoly gPoly::DivideByPolynomial(const gPoly &den) const { - gPoly zero(Space, (T)0); + gPoly zero(Space, static_cast(0)); if (den == zero) { - throw Gambit::ZeroDivideException(); + throw ZeroDivideException(); } // assumes exact divisibility! @@ -413,7 +184,7 @@ template gPoly gPoly::PartialDerivative(int varnumber) const int j = 1; while (j <= newPoly.Terms.size()) { - if (newPoly.Terms[j].Coef() == (T)0) { + if (newPoly.Terms[j].Coef() == static_cast(0)) { newPoly.Terms.erase(std::next(newPoly.Terms.begin(), j - 1)); } else { @@ -421,31 +192,27 @@ template gPoly gPoly::PartialDerivative(int varnumber) const } } - return (newPoly); + return newPoly; } template gPoly gPoly::LeadingCoefficient(int varnumber) const { gPoly newPoly(*this); - int degree = DegreeOfVar(varnumber); - - newPoly.Terms = Gambit::List>(); + newPoly.Terms = List>(); for (int j = 1; j <= Terms.size(); j++) { if (Terms[j].ExpV()[varnumber] == degree) { newPoly.Terms.push_back( gMono(Terms[j].Coef(), Terms[j].ExpV().WithZeroExponent(varnumber))); } } - - return (newPoly); + return newPoly; } template -gPoly gPoly::TranslateOfMono(const gMono &m, const Gambit::Vector &new_origin) const +gPoly gPoly::TranslateOfMono(const gMono &m, const Vector &new_origin) const { - gPoly answer(GetSpace(), (T)1); - + gPoly answer(GetSpace(), static_cast(1)); for (int i = 1; i <= Dmnsn(); i++) { if (m.ExpV()[i] > 0) { gPoly lt(GetSpace(), i, 1); @@ -455,13 +222,11 @@ gPoly gPoly::TranslateOfMono(const gMono &m, const Gambit::Vector &n } } } - answer *= m.Coef(); - return answer; } -template gPoly gPoly::TranslateOfPoly(const Gambit::Vector &new_origin) const +template gPoly gPoly::TranslateOfPoly(const Vector &new_origin) const { gPoly answer(GetSpace()); for (int i = 1; i <= this->MonomialList().size(); i++) { @@ -471,15 +236,13 @@ template gPoly gPoly::TranslateOfPoly(const Gambit::Vector &n } template -gPoly gPoly::MonoInNewCoordinates(const gMono &m, const Gambit::SquareMatrix &M) const +gPoly gPoly::MonoInNewCoordinates(const gMono &m, const SquareMatrix &M) const { - // assert(M.NumRows() == Dmnsn()); - - gPoly answer(GetSpace(), (T)1); + gPoly answer(Space, static_cast(1)); for (int i = 1; i <= Dmnsn(); i++) { if (m.ExpV()[i] > 0) { - gPoly linearform(GetSpace(), (T)0); + gPoly linearform(Space, static_cast(0)); for (int j = 1; j <= Dmnsn(); j++) { ExponentVector exps(GetSpace(), j, 1); linearform += gPoly(GetSpace(), exps, M(i, j)); @@ -489,90 +252,37 @@ gPoly gPoly::MonoInNewCoordinates(const gMono &m, const Gambit::SquareM } } } - answer *= m.Coef(); - return answer; } -template gPoly gPoly::PolyInNewCoordinates(const Gambit::SquareMatrix &M) const +template gPoly gPoly::PolyInNewCoordinates(const SquareMatrix &M) const { - gPoly answer(GetSpace()); - for (int i = 1; i <= MonomialList().size(); i++) { - answer += MonoInNewCoordinates(MonomialList()[i], M); + gPoly answer(Space); + for (const auto &term : Terms) { + answer += MonoInNewCoordinates(term, M); } return answer; } template T gPoly::MaximalValueOfNonlinearPart(const T &radius) const { - T maxcon = (T)0; - for (int i = 1; i <= MonomialList().size(); i++) { - if (MonomialList()[i].TotalDegree() > 1) { - maxcon += MonomialList()[i].Coef() * pow(radius, MonomialList()[i].TotalDegree()); + T maxcon = static_cast(0); + for (const auto &term : Terms) { + if (term.TotalDegree() > 1) { + maxcon += term.Coef() * pow(radius, term.TotalDegree()); } } - return maxcon; } -//--------------------------- -// Global Operators -//--------------------------- - -template gPoly operator*(const T &val, const gPoly &poly) +template gPoly gPoly::Normalize() const { - gPoly prod(poly); - prod *= val; - return prod; -} - -template gPoly operator*(const gPoly &poly, const T &val) { return val * poly; } - -template gPoly operator+(const T &val, const gPoly &poly) -{ - gPoly prod(poly); - prod += val; - return prod; -} - -template gPoly operator+(const gPoly &poly, const T &val) { return val + poly; } - -//---------------------------------- -// Conversion -//---------------------------------- - -template gPoly ToDouble(const gPoly &given) -{ - gPoly answer(given.GetSpace()); - Gambit::List> list = given.MonomialList(); - for (int i = 1; i <= list.size(); i++) { - auto nextcoef = (double)list[i].Coef(); - gPoly next(given.GetSpace(), list[i].ExpV(), nextcoef); - answer += next; + auto maxcoeff = + std::max_element(Terms.begin(), Terms.end(), + [](const gMono &a, const gMono &b) { return a.Coef() < b.Coef(); }); + if (maxcoeff->Coef() < static_cast(0.000000001)) { + return *this; } - - return answer; -} - -template gPoly NormalizationOfPoly(const gPoly &given) -{ - Gambit::List> list = given.MonomialList(); - double maxcoeff = 0.0; - for (int i = 1; i <= list.size(); i++) { - maxcoeff = std::max(maxcoeff, (double)Gambit::abs((double)list[i].Coef())); - } - - if (maxcoeff < 0.000000001) { - return ToDouble(given); - } - - gPoly answer(given.GetSpace()); - for (int i = 1; i <= list.size(); i++) { - double nextcoef = (double)list[i].Coef() / maxcoeff; - gPoly next(given.GetSpace(), list[i].ExpV(), nextcoef); - answer += next; - } - - return answer; + return *this / maxcoeff->Coef(); } diff --git a/src/solvers/enumpoly/gpolylst.imp b/src/solvers/enumpoly/gpolylst.imp index eb39761e3..4ee75839d 100644 --- a/src/solvers/enumpoly/gpolylst.imp +++ b/src/solvers/enumpoly/gpolylst.imp @@ -176,7 +176,7 @@ template Gambit::List> gPolyList::NormalizedList() co { Gambit::List> newlist; for (const auto &v : List) { - newlist.push_back(gPoly(NormalizationOfPoly(*v))); + newlist.push_back(v->Normalize()); } return newlist; } From c76d0742c767b3c4100cecfc781935da7fff18c4 Mon Sep 17 00:00:00 2001 From: Ted Turocy Date: Tue, 17 Dec 2024 12:11:23 +0000 Subject: [PATCH 44/99] Update wxWidgets to 3.2 in ubuntu build --- .github/workflows/tools.yml | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/.github/workflows/tools.yml b/.github/workflows/tools.yml index ffb5d3afb..08e66c385 100644 --- a/.github/workflows/tools.yml +++ b/.github/workflows/tools.yml @@ -16,13 +16,7 @@ jobs: sudo apt-add-repository 'deb https://repos.codelite.org/wx3.1.5/ubuntu/ focal universe' sudo apt-get update sudo apt-get install -y automake autoconf - sudo apt-get install -y libwxbase3.1-0-unofficial \ - libwxbase3.1unofficial-dev \ - libwxgtk3.1-0-unofficial \ - libwxgtk3.1unofficial-dev \ - wx3.1-headers \ - wx-common \ - libnotify-dev + sudo apt-get install -y libwxgtk3.2-dev - name: Configure build with autotools run: | aclocal From 45020650370430c9ef74e7830c212e2839abf48a Mon Sep 17 00:00:00 2001 From: Ted Turocy Date: Wed, 18 Dec 2024 15:07:09 +0000 Subject: [PATCH 45/99] Modernisation of Vector implementation This modernises the implementation of Vector, principally by writing the operations using STL algorithms for the most part. Because Vector is now essentially a thin wrapper around Array combined with the appropriate STL algorithms, it can now be a header-only class. --- Makefile.am | 2 - src/core/array.h | 12 ++ src/core/matrix.imp | 4 +- src/core/vector.cc | 30 ----- src/core/vector.h | 139 +++++++++++++++++---- src/core/vector.imp | 199 ------------------------------- src/solvers/enumpoly/gcomplex.cc | 4 - src/solvers/enumpoly/linrcomb.cc | 3 - 8 files changed, 129 insertions(+), 264 deletions(-) delete mode 100644 src/core/vector.cc delete mode 100644 src/core/vector.imp diff --git a/Makefile.am b/Makefile.am index 51504aac6..aa2b81be0 100644 --- a/Makefile.am +++ b/Makefile.am @@ -248,7 +248,6 @@ core_SOURCES = \ src/core/array.h \ src/core/list.h \ src/core/vector.h \ - src/core/vector.imp \ src/core/recarray.h \ src/core/matrix.h \ src/core/matrix.imp \ @@ -258,7 +257,6 @@ core_SOURCES = \ src/core/integer.h \ src/core/rational.cc \ src/core/rational.h \ - src/core/vector.cc \ src/core/matrix.cc \ src/core/sqmatrix.cc \ src/core/function.cc \ diff --git a/src/core/array.h b/src/core/array.h index be4923880..223666947 100644 --- a/src/core/array.h +++ b/src/core/array.h @@ -81,6 +81,12 @@ template class Array { 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); @@ -108,6 +114,12 @@ template class Array { 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); diff --git a/src/core/matrix.imp b/src/core/matrix.imp index 9356c5ef2..04d7625a7 100644 --- a/src/core/matrix.imp +++ b/src/core/matrix.imp @@ -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; diff --git a/src/core/vector.cc b/src/core/vector.cc deleted file mode 100644 index 412dd5975..000000000 --- a/src/core/vector.cc +++ /dev/null @@ -1,30 +0,0 @@ -// -// This file is part of Gambit -// Copyright (c) 1994-2024, The Gambit Project (http://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 a2cd0521b..8b6ffb185 100644 --- a/src/core/vector.h +++ b/src/core/vector.h @@ -23,13 +23,20 @@ #ifndef LIBGAMBIT_VECTOR_H #define LIBGAMBIT_VECTOR_H +#include + namespace Gambit { 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.mindex == this->mindex && v.maxdex == this->maxdex); + } public: /** Create a vector of length len, starting at 1 */ @@ -42,35 +49,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->mindex, this->maxdex); + 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 tmp(this->mindex, this->maxdex); + std::transform(tmp.cbegin(), tmp.cend(), tmp.begin(), std::negate<>()); + return tmp; + } + + Vector operator-(const Vector &V) const + { + if (!Check(V)) { + throw DimensionException(); + } + Vector tmp(this->mindex, this->maxdex); + 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->mindex, this->maxdex); + 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->mindex, this->maxdex); + std::transform(this->cbegin(), this->cend(), tmp.begin(), [&](const T &v) { return v / c; }); + return tmp; + } + + 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::accumulate(this->begin(), this->end(), static_cast(0), + [](const T &t, const T &v) { return t + v * v; }); + } }; } // end namespace Gambit diff --git a/src/core/vector.imp b/src/core/vector.imp deleted file mode 100644 index 1c0a1cd5b..000000000 --- a/src/core/vector.imp +++ /dev/null @@ -1,199 +0,0 @@ -// -// This file is part of Gambit -// Copyright (c) 1994-2024, The Gambit Project (http://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/solvers/enumpoly/gcomplex.cc b/src/solvers/enumpoly/gcomplex.cc index 063de3fe6..016fda390 100644 --- a/src/solvers/enumpoly/gcomplex.cc +++ b/src/solvers/enumpoly/gcomplex.cc @@ -77,7 +77,3 @@ gComplex pow(const gComplex &x, long y) return answer; } } - -#include "core/vector.imp" - -template class Gambit::Vector; diff --git a/src/solvers/enumpoly/linrcomb.cc b/src/solvers/enumpoly/linrcomb.cc index dc625101a..ef56e1719 100644 --- a/src/solvers/enumpoly/linrcomb.cc +++ b/src/solvers/enumpoly/linrcomb.cc @@ -21,8 +21,5 @@ // #include "linrcomb.imp" -#include "core/vector.imp" -#include "core/matrix.imp" template class LinearCombination; -// template class LinearCombination; From 45d8d106706a5dc765247a0f52d233ed79604153 Mon Sep 17 00:00:00 2001 From: Ted Turocy Date: Tue, 17 Dec 2024 13:11:48 +0000 Subject: [PATCH 46/99] Rewrite some Array operations to be more std::vector-like --- src/core/array.h | 29 ++++++--- src/games/game.cc | 3 +- src/games/game.h | 1 - src/games/gameexpl.cc | 2 +- src/games/gametable.cc | 3 +- src/games/gametree.cc | 85 ++++++++++--------------- src/games/gametree.h | 5 -- src/games/stratpure.cc | 2 +- src/gui/analysis.cc | 12 ++-- src/gui/app.h | 5 +- src/gui/dlefglogit.cc | 6 +- src/gui/dlnfglogit.cc | 8 +-- src/gui/efglayout.cc | 90 +++++++++++++-------------- src/gui/efglayout.h | 2 +- src/gui/efgpanel.cc | 4 +- src/gui/gamedoc.h | 4 +- src/gui/nfgpanel.cc | 4 +- src/gui/nfgtable.cc | 6 +- src/solvers/enummixed/enummixed.cc | 10 +-- src/solvers/enumpoly/behavextend.cc | 10 +-- src/solvers/enumpoly/gameseq.h | 2 +- src/solvers/enumpoly/gpartltr.imp | 8 +-- src/solvers/enumpoly/gpoly.h | 6 +- src/solvers/enumpoly/gpoly.imp | 16 ++--- src/solvers/enumpoly/gpolylst.imp | 16 ++--- src/solvers/enumpoly/gtree.imp | 6 +- src/solvers/enumpoly/ndarray.h | 5 +- src/solvers/enumpoly/quiksolv.imp | 5 +- src/solvers/linalg/lptab.imp | 2 +- src/solvers/linalg/ludecomp.imp | 2 +- src/solvers/linalg/vertenum.h | 2 +- src/solvers/logit/efglogit.cc | 4 +- src/solvers/logit/nfglogit.cc | 2 +- src/solvers/nashsupport/nfgsupport.cc | 2 +- src/solvers/simpdiv/simpdiv.cc | 6 +- src/tools/enummixed/enummixed.cc | 4 +- src/tools/enumpoly/enumpoly.cc | 4 +- src/tools/gt/nfggt.cc | 2 +- src/tools/liap/liap.cc | 8 +-- src/tools/logit/logit.cc | 2 +- src/tools/simpdiv/nfgsimpdiv.cc | 2 +- 41 files changed, 193 insertions(+), 204 deletions(-) diff --git a/src/core/array.h b/src/core/array.h index 223666947..1094a93b8 100644 --- a/src/core/array.h +++ b/src/core/array.h @@ -62,12 +62,14 @@ template class Array { public: class iterator { + friend class Array; + private: Array *m_array; int m_index; public: - using iterator_category = std::forward_iterator_tag; + using iterator_category = std::bidirectional_iterator_tag; using difference_type = std::ptrdiff_t; using value_type = T; using pointer = value_type *; @@ -81,6 +83,11 @@ template class Array { m_index++; return *this; } + iterator &operator--() + { + m_index--; + return *this; + } iterator operator++(int) { auto ret = *this; @@ -100,7 +107,7 @@ template class Array { int m_index; public: - using iterator_category = std::forward_iterator_tag; + using iterator_category = std::bidirectional_iterator_tag; using difference_type = std::ptrdiff_t; using value_type = T; using pointer = value_type *; @@ -114,6 +121,11 @@ template class Array { m_index++; return *this; } + const_iterator &operator--() + { + m_index--; + return *this; + } const_iterator operator++(int) { auto ret = *this; @@ -257,9 +269,6 @@ template class Array { ; 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 @@ -276,6 +285,8 @@ template class Array { : ((n > this->maxdex + 1) ? this->maxdex + 1 : n)); } + void erase(iterator pos) { Remove(pos.m_index); } + /// \brief Remove an element from the array. /// /// Remove the element at a given index from the array. Returns the value @@ -333,9 +344,11 @@ template class Array { /// leaving the container with a size of 0. void clear() { - delete[] (this->data + this->mindex); - this->data = 0; - this->maxdex = this->mindex - 1; + if (maxdex >= mindex) { + delete[] (data + mindex); + } + data = nullptr; + maxdex = mindex - 1; } ///@} }; diff --git a/src/games/game.cc b/src/games/game.cc index 000d316f5..bb4588654 100644 --- a/src/games/game.cc +++ b/src/games/game.cc @@ -60,7 +60,8 @@ void GameStrategyRep::DeleteStrategy() } m_player->GetGame()->IncrementVersion(); - m_player->m_strategies.Remove(m_player->m_strategies.Find(this)); + m_player->m_strategies.erase( + std::find(m_player->m_strategies.begin(), m_player->m_strategies.end(), this)); for (int st = 1; st <= m_player->m_strategies.Length(); st++) { m_player->m_strategies[st]->m_number = st; } diff --git a/src/games/game.h b/src/games/game.h index a6eb14294..3f4708ab2 100644 --- a/src/games/game.h +++ b/src/games/game.h @@ -351,7 +351,6 @@ class GameNodeRep : public GameObject { virtual void SetLabel(const std::string &p_label) = 0; virtual int GetNumber() const = 0; - virtual int NumberInInfoset() const = 0; virtual int NumChildren() const = 0; virtual GameNode GetChild(int i) const = 0; diff --git a/src/games/gameexpl.cc b/src/games/gameexpl.cc index a7083e7f1..5aa7313bc 100644 --- a/src/games/gameexpl.cc +++ b/src/games/gameexpl.cc @@ -108,7 +108,7 @@ Array GameExplicitRep::NumStrategies() const { const_cast(this)->BuildComputedValues(); Array dim(m_players.size()); - for (int pl = 1; pl <= m_players.size(); pl++) { + for (size_t pl = 1; pl <= m_players.size(); pl++) { dim[pl] = m_players[pl]->m_strategies.size(); } return dim; diff --git a/src/games/gametable.cc b/src/games/gametable.cc index 4c0e12520..8a3e4cd8d 100644 --- a/src/games/gametable.cc +++ b/src/games/gametable.cc @@ -409,7 +409,8 @@ void GameTableRep::DeleteOutcome(const GameOutcome &p_outcome) m_results[i] = 0; } } - m_outcomes.Remove(m_outcomes.Find(p_outcome))->Invalidate(); + p_outcome->Invalidate(); + m_outcomes.erase(std::find(m_outcomes.begin(), m_outcomes.end(), p_outcome)); for (int outc = 1; outc <= m_outcomes.Length(); outc++) { m_outcomes[outc]->m_number = outc; } diff --git a/src/games/gametree.cc b/src/games/gametree.cc index e1e564f22..1bb9e7ecc 100644 --- a/src/games/gametree.cc +++ b/src/games/gametree.cc @@ -200,7 +200,8 @@ void GameTreeInfosetRep::SetPlayer(GamePlayer p_player) } m_efg->IncrementVersion(); - m_player->m_infosets.Remove(m_player->m_infosets.Find(this)); + m_player->m_infosets.erase( + std::find(m_player->m_infosets.begin(), m_player->m_infosets.end(), this)); m_player = p_player; p_player->m_infosets.push_back(this); @@ -269,13 +270,15 @@ void GameTreeInfosetRep::RemoveAction(int which) void GameTreeInfosetRep::RemoveMember(GameTreeNodeRep *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; - } + m_members.erase(std::find(m_members.begin(), m_members.end(), p_node)); + if (m_members.empty()) { + m_player->m_infosets.erase( + std::find(m_player->m_infosets.begin(), m_player->m_infosets.end(), this)); Invalidate(); + int iset = 1; + for (auto &infoset : m_player->m_infosets) { + infoset->m_number = iset++; + } } } @@ -348,28 +351,18 @@ Array GameTreeNodeRep::GetChildren() const GameNode GameTreeNodeRep::GetNextSibling() const { - if (!m_parent) { + if (!m_parent || m_parent->children.back() == this) { return nullptr; } - if (m_parent->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->children.begin(), m_parent->children.end(), this)); } GameNode GameTreeNodeRep::GetPriorSibling() const { - if (!m_parent) { + if (!m_parent || m_parent->children.front() == this) { return nullptr; } - if (m_parent->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->children.begin(), m_parent->children.end(), this)); } GameAction GameTreeNodeRep::GetPriorAction() const @@ -451,11 +444,12 @@ void GameTreeNodeRep::DeleteParent() m_efg->IncrementVersion(); GameTreeNodeRep *oldParent = m_parent; - oldParent->children.Remove(oldParent->children.Find(this)); + oldParent->children.erase( + std::find(oldParent->children.begin(), oldParent->children.end(), this)); oldParent->DeleteTree(); m_parent = oldParent->m_parent; if (m_parent) { - m_parent->children[m_parent->children.Find(oldParent)] = this; + std::replace(m_parent->children.begin(), m_parent->children.end(), oldParent, this); } else { m_efg->m_root = this; @@ -538,21 +532,9 @@ void GameTreeNodeRep::MoveTree(GameNode p_src) } 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; - } - + std::iter_swap(std::find(src->m_parent->children.begin(), src->m_parent->children.end(), src), + std::find(m_parent->children.begin(), m_parent->children.end(), this)); + std::swap(src->m_parent, m_parent); m_label = ""; outcome = nullptr; @@ -675,7 +657,7 @@ GameInfoset GameTreeNodeRep::InsertMove(GameInfoset p_infoset) dynamic_cast(p_infoset.operator->())->AddMember(newNode); if (m_parent) { - m_parent->children[m_parent->children.Find(this)] = newNode; + std::replace(m_parent->children.begin(), m_parent->children.end(), this, newNode); } else { m_efg->m_root = newNode; @@ -886,12 +868,12 @@ void GameTreeRep::Canonicalize() 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(); } - m_computedValues = false; } @@ -900,13 +882,10 @@ void GameTreeRep::BuildComputedValues() if (m_computedValues) { return; } - Canonicalize(); - - for (int pl = 1; pl <= m_players.Length(); pl++) { - m_players[pl]->MakeReducedStrats(m_root, nullptr); + for (const auto &player : m_players) { + player->MakeReducedStrats(m_root, nullptr); } - m_computedValues = true; } @@ -1073,9 +1052,11 @@ 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(); + int outc = 1; + for (auto &outcome : m_outcomes) { + outcome->m_number = outc++; } ClearComputedValues(); } @@ -1110,7 +1091,7 @@ Game GameTreeRep::SetChanceProbs(const GameInfoset &p_infoset, const ArrayNumActions() != p_probs.size()) { + if (p_infoset->NumActions() != static_cast(p_probs.size())) { throw DimensionException("The number of probabilities given must match the number of actions"); } IncrementVersion(); diff --git a/src/games/gametree.h b/src/games/gametree.h index 46a1df6d3..12b1b3eae 100644 --- a/src/games/gametree.h +++ b/src/games/gametree.h @@ -156,11 +156,6 @@ class GameTreeNodeRep : public GameNodeRep { 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 diff --git a/src/games/stratpure.cc b/src/games/stratpure.cc index 13a6b7a85..7eb84bf3b 100644 --- a/src/games/stratpure.cc +++ b/src/games/stratpure.cc @@ -32,7 +32,7 @@ namespace Gambit { PureStrategyProfileRep::PureStrategyProfileRep(const Game &p_game) : m_nfg(p_game), m_profile(p_game->NumPlayers()) { - for (size_t pl = 1; pl <= m_nfg->NumPlayers(); pl++) { + for (int pl = 1; pl <= m_nfg->NumPlayers(); pl++) { m_profile[pl] = m_nfg->GetPlayer(pl)->GetStrategy(1); } } diff --git a/src/gui/analysis.cc b/src/gui/analysis.cc index 354133b5b..0924582db 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())); } @@ -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())); } @@ -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.h b/src/gui/app.h index 98bda15a8..e44725884 100644 --- a/src/gui/app.h +++ b/src/gui/app.h @@ -68,7 +68,10 @@ 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; //@} }; diff --git a/src/gui/dlefglogit.cc b/src/gui/dlefglogit.cc index e04a5dc57..4d763d5fc 100644 --- a/src/gui/dlefglogit.cc +++ b/src/gui/dlefglogit.cc @@ -178,7 +178,7 @@ 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())); } @@ -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); diff --git a/src/gui/dlnfglogit.cc b/src/gui/dlnfglogit.cc index 925d0b561..1da4fc182 100644 --- a/src/gui/dlnfglogit.cc +++ b/src/gui/dlnfglogit.cc @@ -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())); } @@ -456,8 +456,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)); @@ -605,7 +605,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); diff --git a/src/gui/efglayout.cc b/src/gui/efglayout.cc index 3aa5d5d9c..861cb73bd 100644 --- a/src/gui/efglayout.cc +++ b/src/gui/efglayout.cc @@ -325,9 +325,9 @@ gbtTreeLayout::gbtTreeLayout(gbtEfgDisplay *p_parent, gbtGameDocument *p_doc) Gambit::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; @@ -335,9 +335,9 @@ Gambit::GameNode gbtTreeLayout::NodeHitTest(int p_x, int p_y) const Gambit::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; @@ -345,9 +345,9 @@ Gambit::GameNode gbtTreeLayout::OutcomeHitTest(int p_x, int p_y) const Gambit::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; @@ -355,9 +355,9 @@ Gambit::GameNode gbtTreeLayout::BranchAboveHitTest(int p_x, int p_y) const Gambit::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; @@ -365,8 +365,7 @@ Gambit::GameNode gbtTreeLayout::BranchBelowHitTest(int p_x, int p_y) const Gambit::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) { @@ -514,9 +513,9 @@ gbtNodeEntry *gbtTreeLayout::GetValidChild(const Gambit::GameNode &e) gbtNodeEntry *gbtTreeLayout::GetEntry(const Gambit::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; @@ -526,9 +525,10 @@ Gambit::GameNode gbtTreeLayout::PriorSameLevel(const Gambit::GameNode &p_node) c { 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(); } } } @@ -539,10 +539,12 @@ Gambit::GameNode gbtTreeLayout::NextSameLevel(const Gambit::GameNode &p_node) co { 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; @@ -578,7 +580,9 @@ int gbtTreeLayout::LayoutSubtree(const Gambit::GameNode &p_node, if (!p_node->GetPlayer()->IsChance() && !p_support.Contains(p_node->GetInfoset()->GetAction(i))) { - m_nodeList[p_node->GetChild(i)->GetNumber()]->SetInSupport(false); + (*std::find_if(m_nodeList.begin(), m_nodeList.end(), [&](const gbtNodeEntry *e) { + return e->GetNode() == p_node->GetChild(i); + }))->SetInSupport(false); } } entry->SetY((y1 + yn) / 2); @@ -632,8 +636,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 +648,7 @@ gbtNodeEntry *gbtTreeLayout::NextInfoset(gbtNodeEntry *e) return e1; } } + ++entry; } return nullptr; } @@ -655,14 +661,12 @@ 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]; + 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) { @@ -684,8 +688,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); @@ -728,8 +731,7 @@ void gbtTreeLayout::UpdateTableInfosets() 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 +741,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,8 +752,7 @@ 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())); } } @@ -762,7 +762,7 @@ void gbtTreeLayout::Layout(const Gambit::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); } @@ -815,10 +815,10 @@ void gbtTreeLayout::BuildNodeList(const Gambit::GameNode &p_node, void gbtTreeLayout::BuildNodeList(const Gambit::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 +826,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())); @@ -877,8 +876,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) { diff --git a/src/gui/efglayout.h b/src/gui/efglayout.h index 5a419bd43..b0489686c 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 3f0f86015..711b27c2f 100644 --- a/src/gui/efgpanel.cc +++ b/src/gui/efgpanel.cc @@ -622,8 +622,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); diff --git a/src/gui/gamedoc.h b/src/gui/gamedoc.h index 0a1973b38..1f6ae07ed 100644 --- a/src/gui/gamedoc.h +++ b/src/gui/gamedoc.h @@ -216,8 +216,8 @@ class gbtGameDocument { 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; } } diff --git a/src/gui/nfgpanel.cc b/src/gui/nfgpanel.cc index 0dba2582b..10d61b1c8 100644 --- a/src/gui/nfgpanel.cc +++ b/src/gui/nfgpanel.cc @@ -434,8 +434,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/nfgtable.cc b/src/gui/nfgtable.cc index d4ba9e534..aaa1fd885 100644 --- a/src/gui/nfgtable.cc +++ b/src/gui/nfgtable.cc @@ -999,7 +999,7 @@ 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 (!contains(m_rowPlayers, pl) && !contains(m_colPlayers, pl)) { m_rowPlayers.push_back(pl); } } @@ -1060,7 +1060,7 @@ bool gbtTableWidget::ShowDominance() const { return m_nfgPanel->IsDominanceShown void gbtTableWidget::SetRowPlayer(int index, int pl) { - if (m_rowPlayers.Contains(pl)) { + if (contains(m_rowPlayers, pl)) { int oldIndex = m_rowPlayers.Find(pl); m_rowPlayers.Remove(oldIndex); if (index > oldIndex) { @@ -1108,7 +1108,7 @@ int gbtTableWidget::RowToStrategy(int player, int row) const void gbtTableWidget::SetColPlayer(int index, int pl) { - if (m_colPlayers.Contains(pl)) { + if (contains(m_colPlayers, pl)) { int oldIndex = m_colPlayers.Find(pl); m_colPlayers.Remove(oldIndex); if (index > oldIndex) { diff --git a/src/solvers/enummixed/enummixed.cc b/src/solvers/enummixed/enummixed.cc index 672c3cc58..6c48b50c8 100644 --- a/src/solvers/enummixed/enummixed.cc +++ b/src/solvers/enummixed/enummixed.cc @@ -35,13 +35,13 @@ List>> EnumMixedStrategySolution::GetCliques() c { if (m_cliques1.empty()) { // Cliques are generated on demand - int n = m_node1.size(); + auto n = m_node1.size(); if (m_node2.size() != n) { throw DimensionException(); } Array edgelist(n); - for (int i = 1; i <= n; i++) { + for (size_t i = 1; i <= n; i++) { edgelist[i].node1 = m_node1[i]; edgelist[i].node2 = m_node2[i]; } @@ -52,7 +52,7 @@ List>> EnumMixedStrategySolution::GetCliques() c } List>> solution; - for (int cl = 1; cl <= m_cliques1.size(); cl++) { + for (size_t cl = 1; cl <= m_cliques1.size(); cl++) { solution.push_back(List>()); for (int i = 1; i <= m_cliques1[cl].Length(); i++) { for (int j = 1; j <= m_cliques2[cl].Length(); j++) { @@ -131,9 +131,9 @@ EnumMixedStrategySolver::SolveDetailed(const Game &p_game) const Array vert1id(solution->m_v1); Array vert2id(solution->m_v2); - for (int i = 1; i <= vert1id.size(); vert1id[i++] = 0) + for (size_t i = 1; i <= vert1id.size(); vert1id[i++] = 0) ; - for (int i = 1; i <= vert2id.size(); vert2id[i++] = 0) + for (size_t i = 1; i <= vert2id.size(); vert2id[i++] = 0) ; int i = 0; diff --git a/src/solvers/enumpoly/behavextend.cc b/src/solvers/enumpoly/behavextend.cc index 03fa78b1e..769091a3d 100644 --- a/src/solvers/enumpoly/behavextend.cc +++ b/src/solvers/enumpoly/behavextend.cc @@ -58,7 +58,7 @@ void DeviationInfosets(List &answer, const BehaviorSupportProfile & } GameInfoset iset = child->GetInfoset(); if (iset->GetPlayer() == pl) { - int insert = 0; + size_t insert = 0; bool done = false; while (!done) { insert++; @@ -118,14 +118,14 @@ std::list DeviationSupports(const BehaviorSupportProfile BehaviorSupportProfile new_supp(big_supp); - for (int i = 1; i <= isetlist.size(); i++) { + for (size_t i = 1; i <= isetlist.size(); i++) { for (int j = 1; j < isetlist[i]->NumActions(); j++) { new_supp.RemoveAction(isetlist[i]->GetAction(j)); } new_supp.AddAction(isetlist[i]->GetAction(1)); active_act_no[i] = 1; - for (int k = 1; k < i; k++) { + for (size_t k = 1; k < i; k++) { if (isetlist[k]->Precedes(isetlist[i]->GetMember(1))) { if (isetlist[k]->GetAction(1)->Precedes(isetlist[i]->GetMember(1))) { new_supp.RemoveAction(isetlist[i]->GetAction(1)); @@ -146,11 +146,11 @@ std::list DeviationSupports(const BehaviorSupportProfile new_supp.RemoveAction(isetlist[iset_cursor]->GetAction(active_act_no[iset_cursor])); active_act_no[iset_cursor]++; new_supp.AddAction(isetlist[iset_cursor]->GetAction(active_act_no[iset_cursor])); - for (int k = iset_cursor + 1; k <= isetlist.size(); k++) { + for (size_t k = iset_cursor + 1; k <= isetlist.size(); k++) { if (active_act_no[k] > 0) { new_supp.RemoveAction(isetlist[k]->GetAction(1)); } - int h = 1; + size_t h = 1; bool active = true; while (active && h < k) { if (isetlist[h]->Precedes(isetlist[k]->GetMember(1))) { diff --git a/src/solvers/enumpoly/gameseq.h b/src/solvers/enumpoly/gameseq.h index ca2656ac6..cc20bd871 100644 --- a/src/solvers/enumpoly/gameseq.h +++ b/src/solvers/enumpoly/gameseq.h @@ -202,7 +202,7 @@ class GameSequenceForm { private: const GameSequenceForm *m_sfg; bool m_end{false}; - std::map m_indices; + std::map m_indices; public: using iterator_category = std::input_iterator_tag; diff --git a/src/solvers/enumpoly/gpartltr.imp b/src/solvers/enumpoly/gpartltr.imp index 18472ec43..d4be575f2 100644 --- a/src/solvers/enumpoly/gpartltr.imp +++ b/src/solvers/enumpoly/gpartltr.imp @@ -94,7 +94,7 @@ T TreeOfPartials::MaximalNonconstantContributionRECURSIVE( if (n->GetEldest() != nullptr) { Gambit::List> *> children = PartialTree.Children(n); - for (int i = 1; i <= children.size(); i++) { + for (size_t i = 1; i <= children.size(); i++) { wrtos[i]++; T increment = children[i]->GetData().Evaluate(p); @@ -130,7 +130,7 @@ T TreeOfPartials::MaximalNonconstantDifferenceRECURSIVE( if (n1->GetEldest() != nullptr && n2->GetEldest() != nullptr) { Gambit::List> *> children1 = PartialTree.Children(n1); Gambit::List> *> children2 = PartialTree.Children(n2); - for (int i = 1; i <= children1.size(); i++) { + for (size_t i = 1; i <= children1.size(); i++) { wrtos[i]++; T increment = children1[i]->GetData().Evaluate(p) - children2[i]->GetData().Evaluate(p); @@ -156,7 +156,7 @@ T TreeOfPartials::MaximalNonconstantDifferenceRECURSIVE( else if (n1->GetEldest() != nullptr && n2->GetEldest() == nullptr) { auto children1 = PartialTree.Children(n1); - for (int i = 1; i <= children1.size(); i++) { + for (size_t i = 1; i <= children1.size(); i++) { wrtos[i]++; T increment = children1[i]->GetData().Evaluate(p); @@ -181,7 +181,7 @@ T TreeOfPartials::MaximalNonconstantDifferenceRECURSIVE( else if (n1->GetEldest() == nullptr && n2->GetEldest() != nullptr) { auto children2 = PartialTree.Children(n2); - for (int i = 1; i <= children2.size(); i++) { + for (size_t i = 1; i <= children2.size(); i++) { wrtos[i]++; T increment = children2[i]->GetData().Evaluate(p); diff --git a/src/solvers/enumpoly/gpoly.h b/src/solvers/enumpoly/gpoly.h index 34f4479ea..47d9bbb38 100644 --- a/src/solvers/enumpoly/gpoly.h +++ b/src/solvers/enumpoly/gpoly.h @@ -270,7 +270,7 @@ template class gPoly { gPoly operator-() const { gPoly neg(*this); - for (int j = 1; j <= Terms.size(); j++) { + for (size_t j = 1; j <= Terms.size(); j++) { neg.Terms[j] = -Terms[j]; } return neg; @@ -284,7 +284,7 @@ template class gPoly { void operator-=(const gPoly &p) { gPoly neg = p; - for (int i = 1; i <= neg.Terms.size(); i++) { + for (size_t i = 1; i <= neg.Terms.size(); i++) { neg.Terms[i] = -neg.Terms[i]; } Terms = Adder(Terms, neg.Terms); @@ -318,7 +318,7 @@ template class gPoly { void operator*=(const gPoly &p) { Terms = Mult(Terms, p.Terms); } void operator*=(const T &val) { - for (int j = 1; j <= Terms.size(); j++) { + for (size_t j = 1; j <= Terms.size(); j++) { Terms[j] *= val; } } diff --git a/src/solvers/enumpoly/gpoly.imp b/src/solvers/enumpoly/gpoly.imp index c4010ab55..87420f7d9 100644 --- a/src/solvers/enumpoly/gpoly.imp +++ b/src/solvers/enumpoly/gpoly.imp @@ -44,8 +44,8 @@ List> gPoly::Adder(const List> &One, const List> & List> answer; - int i = 1; - int j = 1; + size_t i = 1; + size_t j = 1; while (i <= One.size() || j <= Two.size()) { if (i > One.size()) { answer.push_back(Two[j]); @@ -86,8 +86,8 @@ List> gPoly::Mult(const List> &One, const List> &T return answer; } - for (int i = 1; i <= One.size(); i++) { - for (int j = 1; j <= Two.size(); j++) { + for (size_t i = 1; i <= One.size(); i++) { + for (size_t j = 1; j <= Two.size(); j++) { gMono next = One[i] * Two[j]; if (answer.empty()) { @@ -177,12 +177,12 @@ template gPoly gPoly::PartialDerivative(int varnumber) const { gPoly newPoly(*this); - for (int i = 1; i <= newPoly.Terms.size(); i++) { + for (size_t i = 1; i <= newPoly.Terms.size(); i++) { newPoly.Terms[i] = gMono(newPoly.Terms[i].Coef() * (T)newPoly.Terms[i].ExpV()[varnumber], newPoly.Terms[i].ExpV().WithZeroExponent(varnumber)); } - int j = 1; + size_t j = 1; while (j <= newPoly.Terms.size()) { if (newPoly.Terms[j].Coef() == static_cast(0)) { newPoly.Terms.erase(std::next(newPoly.Terms.begin(), j - 1)); @@ -200,7 +200,7 @@ template gPoly gPoly::LeadingCoefficient(int varnumber) const gPoly newPoly(*this); int degree = DegreeOfVar(varnumber); newPoly.Terms = List>(); - for (int j = 1; j <= Terms.size(); j++) { + for (size_t j = 1; j <= Terms.size(); j++) { if (Terms[j].ExpV()[varnumber] == degree) { newPoly.Terms.push_back( gMono(Terms[j].Coef(), Terms[j].ExpV().WithZeroExponent(varnumber))); @@ -229,7 +229,7 @@ gPoly gPoly::TranslateOfMono(const gMono &m, const Vector &new_origi template gPoly gPoly::TranslateOfPoly(const Vector &new_origin) const { gPoly answer(GetSpace()); - for (int i = 1; i <= this->MonomialList().size(); i++) { + for (size_t i = 1; i <= this->MonomialList().size(); i++) { answer += TranslateOfMono(this->MonomialList()[i], new_origin); } return answer; diff --git a/src/solvers/enumpoly/gpolylst.imp b/src/solvers/enumpoly/gpolylst.imp index 4ee75839d..3fa88b7ac 100644 --- a/src/solvers/enumpoly/gpolylst.imp +++ b/src/solvers/enumpoly/gpolylst.imp @@ -45,7 +45,7 @@ Gambit::List InteriorSegment(const Gambit::List &p_list, int first, int la template gPolyList::gPolyList(const VariableSpace *sp, const Gambit::List *> &plist) : Space(sp) { - for (int ii = 1; ii <= plist.size(); ii++) { + for (size_t ii = 1; ii <= plist.size(); ii++) { auto *temp = new gPoly(*plist[ii]); List.push_back(temp); } @@ -54,7 +54,7 @@ gPolyList::gPolyList(const VariableSpace *sp, const Gambit::List *> template gPolyList::gPolyList(const VariableSpace *sp, const Gambit::List> &list) : Space(sp) { - for (int ii = 1; ii <= list.size(); ii++) { + for (size_t ii = 1; ii <= list.size(); ii++) { auto *temp = new gPoly(list[ii]); List.push_back(temp); } @@ -62,7 +62,7 @@ gPolyList::gPolyList(const VariableSpace *sp, const Gambit::List> &l template gPolyList::gPolyList(const gPolyList &lst) : Space(lst.Space) { - for (int ii = 1; ii <= lst.List.size(); ii++) { + for (size_t ii = 1; ii <= lst.List.size(); ii++) { auto *temp = new gPoly(*(lst.List[ii])); List.push_back(temp); } @@ -70,7 +70,7 @@ template gPolyList::gPolyList(const gPolyList &lst) : Space(lst. template gPolyList::~gPolyList() { - for (int ii = 1; ii <= List.size(); ii++) { + for (size_t ii = 1; ii <= List.size(); ii++) { delete List[ii]; } } @@ -82,12 +82,12 @@ template gPolyList::~gPolyList() template gPolyList &gPolyList::operator=(const gPolyList &rhs) { if (*this != rhs) { - for (int ii = List.size(); ii >= 1; ii--) { + for (size_t ii = List.size(); ii >= 1; ii--) { delete List[ii]; List.erase(std::next(List.begin(), ii - 1)); } - for (int ii = 1; ii <= rhs.List.size(); ii++) { + for (size_t ii = 1; ii <= rhs.List.size(); ii++) { auto *temp = new gPoly(*(rhs.List[ii])); List.push_back(temp); } @@ -103,7 +103,7 @@ template bool gPolyList::operator==(const gPolyList &rhs) const if (List.size() != rhs.List.size()) { return false; } - for (int j = 1; j <= List.size(); j++) { + for (size_t j = 1; j <= List.size(); j++) { if (*List[j] != *(rhs.List[j])) { return false; } @@ -165,7 +165,7 @@ template bool gPolyList::IsMultiaffine() const template Gambit::Vector gPolyList::Evaluate(const Gambit::Vector &v) const { Gambit::Vector answer(Length()); - for (int ii = 1; ii <= List.size(); ii++) { + for (size_t ii = 1; ii <= List.size(); ii++) { answer[ii] = List[ii]->Evaluate(v); } diff --git a/src/solvers/enumpoly/gtree.imp b/src/solvers/enumpoly/gtree.imp index eb1081136..f57629377 100644 --- a/src/solvers/enumpoly/gtree.imp +++ b/src/solvers/enumpoly/gtree.imp @@ -75,11 +75,11 @@ template void gTree::InsertAt(const T &t, gTreeNode *n) template void gTree::RecursiveCopy(gTreeNode *copyn, const gTreeNode *orign) { auto oldchildren = Children(orign); - for (int i = 1; i <= oldchildren.size(); i++) { + for (size_t i = 1; i <= oldchildren.size(); i++) { InsertAt(oldchildren[i]->data, copyn); } auto newchildren = Children(copyn); - for (int i = 1; i <= newchildren.size(); i++) { + for (size_t i = 1; i <= newchildren.size(); i++) { RecursiveCopy(newchildren[i], oldchildren[i]); } } @@ -160,7 +160,7 @@ bool gTree::SubtreesAreIsomorphic(const gTreeNode *lhs, const gTreeNode if (lchildren.size() != rchildren.size()) { return false; } - for (int i = 1; i <= lchildren.size(); i++) { + for (size_t i = 1; i <= lchildren.size(); i++) { if (!SubtreesAreIsomorphic(lchildren[i], rchildren[i])) { return false; } diff --git a/src/solvers/enumpoly/ndarray.h b/src/solvers/enumpoly/ndarray.h index 8f0b5c264..801e5b43c 100644 --- a/src/solvers/enumpoly/ndarray.h +++ b/src/solvers/enumpoly/ndarray.h @@ -61,11 +61,10 @@ template class NDArray { public: NDArray() : m_vector_dim(0) {} explicit NDArray(const Array &p_index_dim, int p_vector_dim) - : m_index_dim(p_index_dim), m_vector_dim(p_vector_dim), + : m_index_dim(p_index_dim), m_vector_dim(p_vector_dim), m_offsets(p_index_dim.size() + 1), m_storage( std::accumulate(m_index_dim.begin(), m_index_dim.end(), 1, std::multiplies()) * - m_vector_dim), - m_offsets(p_index_dim.size() + 1) + m_vector_dim) { m_offsets.front() = 1; std::partial_sum(m_index_dim.begin(), m_index_dim.end(), std::next(m_offsets.begin()), diff --git a/src/solvers/enumpoly/quiksolv.imp b/src/solvers/enumpoly/quiksolv.imp index 5791ba52c..7010616c8 100644 --- a/src/solvers/enumpoly/quiksolv.imp +++ b/src/solvers/enumpoly/quiksolv.imp @@ -451,15 +451,14 @@ void QuikSolv::FindRootsRecursion(Gambit::List> *rootl Gambit::Vector point = r.Center(); if (NewtonRootIsOnlyInRct(r, point)) { - int i; - for (i = NoEquations + 1; i <= System.Length(); i++) { + for (int i = NoEquations + 1; i <= System.Length(); i++) { if (TreesOfPartials[i].ValueOfRootPoly(point) < (double)0) { return; } } bool already_found = false; - for (i = 1; i <= rootlistptr->size(); i++) { + for (size_t i = 1; i <= rootlistptr->size(); i++) { if (fuzzy_equals(point, (*rootlistptr)[i])) { already_found = true; } diff --git a/src/solvers/linalg/lptab.imp b/src/solvers/linalg/lptab.imp index 231b8afeb..58c379300 100644 --- a/src/solvers/linalg/lptab.imp +++ b/src/solvers/linalg/lptab.imp @@ -170,7 +170,7 @@ template std::list> LPTableau::ReversePivots() if (!best_set.empty()) { ratio = solution[best_set[1]] / tmpcol[best_set[1]]; // find max ratio - for (int i = 2; i <= best_set.size(); i++) { + for (size_t i = 2; i <= best_set.size(); i++) { x = solution[best_set[i]] / tmpcol[best_set[i]]; if (this->GtZero(x - ratio)) { ratio = x; diff --git a/src/solvers/linalg/ludecomp.imp b/src/solvers/linalg/ludecomp.imp index 928cadcdf..1543ad449 100644 --- a/src/solvers/linalg/ludecomp.imp +++ b/src/solvers/linalg/ludecomp.imp @@ -320,7 +320,7 @@ void LUDecomposition::yLP_mult(const Vector &y, int j, Vector &ans) con template void LUDecomposition::LPd_Trans(Vector &d) const { Vector scratch(basis.First(), basis.Last()); - for (int j = 0; j < L.size(); j++) { + for (size_t j = 0; j < L.size(); j++) { LPd_mult(d, j, scratch); d = scratch; } diff --git a/src/solvers/linalg/vertenum.h b/src/solvers/linalg/vertenum.h index 32c9a99c8..aec008a7d 100644 --- a/src/solvers/linalg/vertenum.h +++ b/src/solvers/linalg/vertenum.h @@ -44,7 +44,7 @@ namespace Gambit::linalg { template class VertexEnumerator { private: bool mult_opt; - int depth{0}; + size_t depth{0}; int n; // N is the number of columns, which is the # of dimensions. int k; // K is the number of inequalities given. const Matrix &A; diff --git a/src/solvers/logit/efglogit.cc b/src/solvers/logit/efglogit.cc index 1e4a51bf0..16c0cd734 100644 --- a/src/solvers/logit/efglogit.cc +++ b/src/solvers/logit/efglogit.cc @@ -60,7 +60,7 @@ MixedBehaviorProfile PointToProfile(const Game &p_game, const Vector PointToLogProfile(const Game &p_game, const Vector &p_point) { LogBehavProfile profile(p_game); - for (int i = 1; i <= profile.BehaviorProfileLength(); i++) { + for (size_t i = 1; i <= profile.BehaviorProfileLength(); i++) { profile.SetLogProb(i, p_point[i]); } return profile; @@ -69,7 +69,7 @@ LogBehavProfile PointToLogProfile(const Game &p_game, const Vector ProfileToPoint(const LogitQREMixedBehaviorProfile &p_profile) { Vector point(p_profile.size() + 1); - for (int i = 1; i <= p_profile.size(); i++) { + for (size_t i = 1; i <= p_profile.size(); i++) { point[i] = log(p_profile[i]); } point.back() = p_profile.GetLambda(); diff --git a/src/solvers/logit/nfglogit.cc b/src/solvers/logit/nfglogit.cc index bda5bb8ca..5cebb77d4 100644 --- a/src/solvers/logit/nfglogit.cc +++ b/src/solvers/logit/nfglogit.cc @@ -43,7 +43,7 @@ MixedStrategyProfile PointToProfile(const Game &p_game, const Vector ProfileToPoint(const LogitQREMixedStrategyProfile &p_profile) { Vector point(p_profile.size() + 1); - for (int i = 1; i <= p_profile.size(); i++) { + for (size_t i = 1; i <= p_profile.size(); i++) { point[i] = log(p_profile[i]); } point.back() = p_profile.GetLambda(); diff --git a/src/solvers/nashsupport/nfgsupport.cc b/src/solvers/nashsupport/nfgsupport.cc index f5260a5be..a58ad338f 100644 --- a/src/solvers/nashsupport/nfgsupport.cc +++ b/src/solvers/nashsupport/nfgsupport.cc @@ -63,7 +63,7 @@ class CartesianRange { iterator &operator++() { - for (int i = 1; i <= m_sizes.size(); i++) { + for (size_t i = 1; i <= m_sizes.size(); i++) { if (++m_indices[i] <= m_sizes[i]) { return *this; } diff --git a/src/solvers/simpdiv/simpdiv.cc b/src/solvers/simpdiv/simpdiv.cc index 7ab03b767..7ce370b5a 100644 --- a/src/solvers/simpdiv/simpdiv.cc +++ b/src/solvers/simpdiv/simpdiv.cc @@ -37,7 +37,7 @@ template class PVector { : m_values(std::accumulate(p_shape.begin(), p_shape.end(), 0)), m_offsets(p_shape.size()), m_shape(p_shape) { - for (int index = 0, i = 1; i <= m_shape.size(); i++) { + for (size_t index = 0, i = 1; i <= m_shape.size(); i++) { m_offsets[i] = index; index += m_shape[i]; } @@ -107,7 +107,7 @@ Rational NashSimpdivStrategySolver::Simplex(MixedStrategyProfile &y, RectArray labels(y.MixedProfileLength(), 2), pi(y.MixedProfileLength(), 2); PVector U(nstrats), TT(nstrats); PVector ab(nstrats), besty(nstrats), v(nstrats); - for (int i = 1; i <= v.size(); i++) { + for (size_t i = 1; i <= v.size(); i++) { v[i] = y[i]; } besty = static_cast &>(y); @@ -399,7 +399,7 @@ void NashSimpdivStrategySolver::getY(const State &state, MixedStrategyProfile &pi, int k) { x = static_cast &>(v); - for (size_t j = 1; j <= x.GetGame()->NumPlayers(); j++) { + for (int j = 1; j <= x.GetGame()->NumPlayers(); j++) { GamePlayer player = x.GetGame()->GetPlayer(j); for (size_t h = 1; h <= player->GetStrategies().size(); h++) { if (TT(j, h) == 1 || U(j, h) == 1) { diff --git a/src/tools/enummixed/enummixed.cc b/src/tools/enummixed/enummixed.cc index c98c4eabe..cefaaf9d6 100644 --- a/src/tools/enummixed/enummixed.cc +++ b/src/tools/enummixed/enummixed.cc @@ -35,8 +35,8 @@ template void PrintCliques(const List>> &p_cliques, std::shared_ptr> p_renderer) { - for (int cl = 1; cl <= p_cliques.size(); cl++) { - for (int i = 1; i <= p_cliques[cl].size(); i++) { + for (size_t cl = 1; cl <= p_cliques.size(); cl++) { + for (size_t i = 1; i <= p_cliques[cl].size(); i++) { p_renderer->Render(p_cliques[cl][i], "convex-" + lexical_cast(cl)); } } diff --git a/src/tools/enumpoly/enumpoly.cc b/src/tools/enumpoly/enumpoly.cc index 2f5242f50..412b3db71 100644 --- a/src/tools/enumpoly/enumpoly.cc +++ b/src/tools/enumpoly/enumpoly.cc @@ -66,7 +66,7 @@ void PrintProfile(std::ostream &p_stream, const std::string &p_label, const MixedStrategyProfile &p_profile) { p_stream << p_label; - for (int i = 1; i <= p_profile.MixedProfileLength(); i++) { + for (size_t i = 1; i <= p_profile.MixedProfileLength(); i++) { p_stream.setf(std::ios::fixed); p_stream << ',' << std::setprecision(g_numDecimals) << p_profile[i]; } @@ -93,7 +93,7 @@ void PrintProfile(std::ostream &p_stream, const std::string &p_label, const MixedBehaviorProfile &p_profile) { p_stream << p_label; - for (int i = 1; i <= p_profile.BehaviorProfileLength(); i++) { + for (size_t i = 1; i <= p_profile.BehaviorProfileLength(); i++) { p_stream.setf(std::ios::fixed); p_stream << "," << std::setprecision(g_numDecimals) << p_profile[i]; } diff --git a/src/tools/gt/nfggt.cc b/src/tools/gt/nfggt.cc index 46025f35f..b126843fd 100644 --- a/src/tools/gt/nfggt.cc +++ b/src/tools/gt/nfggt.cc @@ -30,7 +30,7 @@ List> ReadStrategyPerturbations(const Game &p_game, List> profiles; while (!p_stream.eof() && !p_stream.bad()) { MixedStrategyProfile p(p_game->NewMixedStrategyProfile(0.0)); - for (int i = 1; i <= p.MixedProfileLength(); i++) { + for (size_t i = 1; i <= p.MixedProfileLength(); i++) { if (p_stream.eof() || p_stream.bad()) { break; } diff --git a/src/tools/liap/liap.cc b/src/tools/liap/liap.cc index ba41d0f23..912788e87 100644 --- a/src/tools/liap/liap.cc +++ b/src/tools/liap/liap.cc @@ -64,7 +64,7 @@ List> ReadStrategyProfiles(const Game &p_game, std: List> profiles; while (!p_stream.eof() && !p_stream.bad()) { MixedStrategyProfile p(p_game->NewMixedStrategyProfile(0.0)); - for (int i = 1; i <= p.MixedProfileLength(); i++) { + for (size_t i = 1; i <= p.MixedProfileLength(); i++) { if (p_stream.eof() || p_stream.bad()) { break; } @@ -97,7 +97,7 @@ List> ReadBehaviorProfiles(const Game &p_game, std: List> profiles; while (!p_stream.eof() && !p_stream.bad()) { MixedBehaviorProfile p(p_game); - for (int i = 1; i <= p.BehaviorProfileLength(); i++) { + for (size_t i = 1; i <= p.BehaviorProfileLength(); i++) { if (p_stream.eof() || p_stream.bad()) { break; } @@ -214,7 +214,7 @@ int main(int argc, char *argv[]) starts = RandomStrategyProfiles(game, numTries); } - for (int i = 1; i <= starts.size(); i++) { + for (size_t i = 1; i <= starts.size(); i++) { std::shared_ptr> renderer( new MixedStrategyCSVRenderer(std::cout, numDecimals)); @@ -238,7 +238,7 @@ int main(int argc, char *argv[]) starts = RandomBehaviorProfiles(game, numTries); } - for (int i = 1; i <= starts.size(); i++) { + for (size_t i = 1; i <= starts.size(); i++) { std::shared_ptr> renderer( new BehavStrategyCSVRenderer(std::cout, numDecimals)); LiapBehaviorSolve(starts[i], maxregret, maxitsN, diff --git a/src/tools/logit/logit.cc b/src/tools/logit/logit.cc index 203c51de2..e1f383997 100644 --- a/src/tools/logit/logit.cc +++ b/src/tools/logit/logit.cc @@ -66,7 +66,7 @@ void PrintHelp(char *progname) // bool ReadProfile(std::istream &p_stream, MixedStrategyProfile &p_profile) { - for (int i = 1; i <= p_profile.MixedProfileLength(); i++) { + for (size_t i = 1; i <= p_profile.MixedProfileLength(); i++) { if (p_stream.eof() || p_stream.bad()) { return false; } diff --git a/src/tools/simpdiv/nfgsimpdiv.cc b/src/tools/simpdiv/nfgsimpdiv.cc index 159d90ec9..9974b5927 100644 --- a/src/tools/simpdiv/nfgsimpdiv.cc +++ b/src/tools/simpdiv/nfgsimpdiv.cc @@ -36,7 +36,7 @@ List> ReadProfiles(const Game &p_game, std::istre List> profiles; while (!p_stream.eof() && !p_stream.bad()) { MixedStrategyProfile p(p_game->NewMixedStrategyProfile(Rational(0))); - for (int i = 1; i <= p.MixedProfileLength(); i++) { + for (size_t i = 1; i <= p.MixedProfileLength(); i++) { if (p_stream.eof() || p_stream.bad()) { break; } From c066dbc5cec01ef838c4a1e432a13c7c9de5768d Mon Sep 17 00:00:00 2001 From: Ted Turocy Date: Mon, 23 Dec 2024 10:55:40 +0000 Subject: [PATCH 47/99] Make Array as similar to std::vector as possible. This aligns the interface of Array to be as similar to std::vector as possible, without (yet) adopting a std::vector implementation. The indexing mechanism has been clarified, with first_index() and last_index() methods which will be more compatible with this being viewable as an extension of std::vector for the use case of needing a contiguous set of indices not necessarily starting at zero. --- src/core/array.h | 265 ++++++++++------------------ src/core/function.cc | 6 +- src/core/recarray.h | 4 +- src/core/vector.h | 17 +- src/games/behavmixed.cc | 2 +- src/games/behavmixed.h | 2 +- src/games/game.cc | 6 +- src/games/gameagg.cc | 4 +- src/games/gamebagg.cc | 4 +- src/games/gameexpl.cc | 4 +- src/games/gametable.cc | 24 +-- src/games/gametree.cc | 76 ++++---- src/gui/analysis.cc | 12 +- src/gui/app.cc | 4 +- src/gui/dleditnode.cc | 4 +- src/gui/dlefglayout.cc | 6 +- src/gui/dlefglogit.cc | 4 +- src/gui/dlnfglogit.cc | 2 +- src/gui/efgpanel.cc | 13 +- src/gui/gamedoc.cc | 43 ++--- src/gui/gamedoc.h | 12 +- src/gui/gameframe.cc | 2 +- src/gui/nfgpanel.cc | 13 +- src/gui/nfgtable.cc | 54 +++--- src/gui/nfgtable.h | 4 +- src/gui/style.cc | 8 +- src/labenski/src/plotdata.cpp | 8 +- src/solvers/enummixed/clique.cc | 2 +- src/solvers/enummixed/enummixed.cc | 10 +- src/solvers/enumpoly/behavextend.cc | 2 +- src/solvers/enumpoly/gpartltr.imp | 16 +- src/solvers/enumpoly/odometer.cc | 7 +- src/solvers/enumpoly/odometer.h | 2 +- src/solvers/enumpoly/quiksolv.imp | 4 +- src/solvers/enumpoly/rectangl.imp | 2 +- src/solvers/liap/efgliap.cc | 2 +- src/solvers/linalg/basis.cc | 24 ++- src/solvers/linalg/btableau.imp | 10 +- src/solvers/linalg/lemketab.imp | 34 ++-- src/solvers/linalg/lhtab.imp | 9 +- src/solvers/linalg/lpsolve.imp | 24 +-- src/solvers/linalg/lptab.h | 2 +- src/solvers/linalg/lptab.imp | 50 +++--- src/solvers/linalg/ludecomp.imp | 24 +-- src/solvers/linalg/tableau.cc | 54 +++--- src/solvers/linalg/vertenum.imp | 4 +- src/solvers/logit/efglogit.cc | 12 +- src/solvers/logit/nfglogit.cc | 6 +- src/solvers/logit/path.cc | 12 +- src/solvers/simpdiv/simpdiv.cc | 2 +- 50 files changed, 424 insertions(+), 493 deletions(-) diff --git a/src/core/array.h b/src/core/array.h index 1094a93b8..3c94aab6b 100644 --- a/src/core/array.h +++ b/src/core/array.h @@ -28,38 +28,63 @@ 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; @@ -102,6 +127,8 @@ template class Array { }; class const_iterator { + friend class Array; + private: const Array *m_array; int m_index; @@ -139,75 +166,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; } @@ -215,144 +226,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); - } - //@} - - /// @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)); - } - - void erase(iterator pos) { Remove(pos.m_index); } - - /// \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() { - if (maxdex >= mindex) { - delete[] (data + mindex); + if (m_length > 0) { + delete[] (m_data + m_offset); } - data = nullptr; - maxdex = mindex - 1; + 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 diff --git a/src/core/function.cc b/src/core/function.cc index 7e6d1c2b5..46a0abf79 100644 --- a/src/core/function.cc +++ b/src/core/function.cc @@ -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 (int 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 (int i = 1; i <= y.size(); i++) { y[i] += alpha * x[i]; } } @@ -330,7 +330,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/recarray.h b/src/core/recarray.h index cd6f05117..a670d6fc2 100644 --- a/src/core/recarray.h +++ b/src/core/recarray.h @@ -112,7 +112,7 @@ template bool RectArray::CheckRow(int row) const template bool RectArray::CheckRow(const Array &v) const { - return (v.First() == mincol && v.Last() == maxcol); + return (v.first_index() == mincol && v.last_index() == maxcol); } template bool RectArray::CheckColumn(int col) const @@ -122,7 +122,7 @@ template bool RectArray::CheckColumn(int col) const template bool RectArray::CheckColumn(const Array &v) const { - return (v.First() == minrow && v.Last() == maxrow); + return (v.first_index() == minrow && v.last_index() == maxrow); } template bool RectArray::Check(int row, int col) const diff --git a/src/core/vector.h b/src/core/vector.h index 8b6ffb185..21e2344c8 100644 --- a/src/core/vector.h +++ b/src/core/vector.h @@ -35,7 +35,7 @@ template class Vector : public Array { // check vector for identical boundaries bool Check(const Vector &v) const { - return (v.mindex == this->mindex && v.maxdex == this->maxdex); + return (v.first_index() == this->first_index() && v.size() == this->size()); } public: @@ -69,7 +69,7 @@ template class Vector : public Array { if (!Check(V)) { throw DimensionException(); } - Vector tmp(this->mindex, this->maxdex); + Vector tmp(this->first_index(), this->last_index()); std::transform(this->cbegin(), this->cend(), V.cbegin(), tmp.begin(), std::plus<>()); return tmp; } @@ -83,19 +83,12 @@ template class Vector : public Array { return *this; } - Vector operator-() const - { - Vector tmp(this->mindex, this->maxdex); - std::transform(tmp.cbegin(), tmp.cend(), tmp.begin(), std::negate<>()); - return tmp; - } - Vector operator-(const Vector &V) const { if (!Check(V)) { throw DimensionException(); } - Vector tmp(this->mindex, this->maxdex); + Vector tmp(this->first_index(), this->last_index()); std::transform(this->cbegin(), this->cend(), V.cbegin(), tmp.begin(), std::minus<>()); return tmp; } @@ -111,7 +104,7 @@ template class Vector : public Array { Vector operator*(const T &c) const { - Vector tmp(this->mindex, this->maxdex); + Vector tmp(this->first_index(), this->last_index()); std::transform(this->cbegin(), this->cend(), tmp.begin(), [&](const T &v) { return v * c; }); return tmp; } @@ -132,7 +125,7 @@ template class Vector : public Array { Vector operator/(const T &c) const { - Vector tmp(this->mindex, this->maxdex); + Vector tmp(this->first_index(), this->last_index()); std::transform(this->cbegin(), this->cend(), tmp.begin(), [&](const T &v) { return v / c; }); return tmp; } diff --git a/src/games/behavmixed.cc b/src/games/behavmixed.cc index 940b5a06a..6015482db 100644 --- a/src/games/behavmixed.cc +++ b/src/games/behavmixed.cc @@ -94,7 +94,7 @@ void MixedBehaviorProfile::RealizationProbs(const MixedStrategyProfile &mp { T prob; - for (int i = 1; i <= node->children.Length(); i++) { + for (int i = 1; i <= node->children.size(); i++) { if (node->GetPlayer() && !node->GetPlayer()->IsChance()) { if (node->GetPlayer() == player) { if (actions[node->GetInfoset()->GetNumber()] == i) { diff --git a/src/games/behavmixed.h b/src/games/behavmixed.h index 9ec3135a3..a0cf8810e 100644 --- a/src/games/behavmixed.h +++ b/src/games/behavmixed.h @@ -158,7 +158,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 diff --git a/src/games/game.cc b/src/games/game.cc index bb4588654..b85471da4 100644 --- a/src/games/game.cc +++ b/src/games/game.cc @@ -62,7 +62,7 @@ void GameStrategyRep::DeleteStrategy() m_player->GetGame()->IncrementVersion(); m_player->m_strategies.erase( std::find(m_player->m_strategies.begin(), m_player->m_strategies.end(), this)); - for (int st = 1; st <= m_player->m_strategies.Length(); st++) { + for (int st = 1; st <= m_player->m_strategies.size(); st++) { m_player->m_strategies[st]->m_number = st; } // m_player->m_game->RebuildTable(); @@ -131,13 +131,13 @@ void GamePlayerRep::MakeStrategy() auto *strategy = new GameStrategyRep(this); m_strategies.push_back(strategy); - strategy->m_number = m_strategies.Length(); + strategy->m_number = m_strategies.size(); strategy->m_behav = c; strategy->m_label = ""; // We generate a default labeling -- probably should be changed in future if (!strategy->m_behav.empty()) { - for (int iset = 1; iset <= strategy->m_behav.Length(); iset++) { + for (int iset = 1; iset <= strategy->m_behav.size(); iset++) { if (strategy->m_behav[iset] > 0) { strategy->m_label += lexical_cast(strategy->m_behav[iset]); } diff --git a/src/games/gameagg.cc b/src/games/gameagg.cc index 1ef4b753a..8dc0c5f52 100644 --- a/src/games/gameagg.cc +++ b/src/games/gameagg.cc @@ -261,13 +261,13 @@ bool GameAGGRep::IsConstSum() const { auto profile = NewPureStrategyProfile(); Rational sum(0); - for (int pl = 1; pl <= m_players.Length(); pl++) { + for (int pl = 1; pl <= m_players.size(); pl++) { sum += profile->GetPayoff(pl); } for (auto iter : StrategyContingencies(Game(this))) { Rational newsum(0); - for (int pl = 1; pl <= m_players.Length(); pl++) { + for (int pl = 1; pl <= m_players.size(); pl++) { newsum += iter->GetPayoff(pl); } if (newsum != sum) { diff --git a/src/games/gamebagg.cc b/src/games/gamebagg.cc index 3b931d7a9..8526cd3d0 100644 --- a/src/games/gamebagg.cc +++ b/src/games/gamebagg.cc @@ -241,7 +241,7 @@ Array GameBAGGRep::NumStrategies() const { Array ns; for (int pl = 1; pl <= NumPlayers(); pl++) { - ns.push_back(m_players[pl]->m_strategies.Length()); + ns.push_back(m_players[pl]->m_strategies.size()); } return ns; } @@ -250,7 +250,7 @@ int GameBAGGRep::MixedProfileLength() const { int res = 0; for (int pl = 1; pl <= NumPlayers(); pl++) { - res += m_players[pl]->m_strategies.Length(); + res += m_players[pl]->m_strategies.size(); } return res; } diff --git a/src/games/gameexpl.cc b/src/games/gameexpl.cc index 5aa7313bc..c85ea813f 100644 --- a/src/games/gameexpl.cc +++ b/src/games/gameexpl.cc @@ -117,8 +117,8 @@ Array GameExplicitRep::NumStrategies() const 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++) { + for (int pl = 1, i = 1; pl <= m_players.size(); pl++) { + for (int st = 1; st <= m_players[pl]->m_strategies.size(); st++, i++) { if (p_index == i) { return m_players[pl]->m_strategies[st]; } diff --git a/src/games/gametable.cc b/src/games/gametable.cc index 8a3e4cd8d..eba14ff3c 100644 --- a/src/games/gametable.cc +++ b/src/games/gametable.cc @@ -282,7 +282,7 @@ int Product(const Array &dim) GameTableRep::GameTableRep(const Array &dim, bool p_sparseOutcomes /* = false */) { m_results = Array(Product(dim)); - for (int pl = 1; pl <= dim.Length(); pl++) { + for (int pl = 1; pl <= dim.size(); pl++) { m_players.push_back(new GamePlayerRep(this, pl, dim[pl])); m_players[pl]->m_label = lexical_cast(pl); for (int st = 1; st <= m_players[pl]->NumStrategies(); st++) { @@ -292,12 +292,12 @@ GameTableRep::GameTableRep(const Array &dim, bool p_sparseOutcomes /* = fal IndexStrategies(); if (p_sparseOutcomes) { - for (int cont = 1; cont <= m_results.Length(); m_results[cont++] = 0) + for (int cont = 1; cont <= m_results.size(); m_results[cont++] = 0) ; } else { - m_outcomes = Array(m_results.Length()); - for (int i = 1; i <= m_outcomes.Length(); i++) { + m_outcomes = Array(m_results.size()); + for (int i = 1; i <= m_outcomes.size(); i++) { m_outcomes[i] = new GameOutcomeRep(this, i); } m_results = m_outcomes; @@ -320,13 +320,13 @@ bool GameTableRep::IsConstSum() const { auto profile = NewPureStrategyProfile(); Rational sum(0); - for (int pl = 1; pl <= m_players.Length(); pl++) { + for (int pl = 1; pl <= m_players.size(); pl++) { sum += profile->GetPayoff(pl); } for (auto iter : StrategyContingencies(Game(this))) { Rational newsum(0); - for (int pl = 1; pl <= m_players.Length(); pl++) { + for (int pl = 1; pl <= m_players.size(); pl++) { newsum += iter->GetPayoff(pl); } if (newsum != sum) { @@ -404,14 +404,14 @@ GamePlayer GameTableRep::NewPlayer() void GameTableRep::DeleteOutcome(const GameOutcome &p_outcome) { IncrementVersion(); - for (int i = 1; i <= m_results.Length(); i++) { + for (int i = 1; i <= m_results.size(); i++) { if (m_results[i] == p_outcome) { m_results[i] = 0; } } p_outcome->Invalidate(); m_outcomes.erase(std::find(m_outcomes.begin(), m_outcomes.end(), p_outcome)); - for (int outc = 1; outc <= m_outcomes.Length(); outc++) { + for (int outc = 1; outc <= m_outcomes.size(); outc++) { m_outcomes[outc]->m_number = outc; } } @@ -453,20 +453,20 @@ 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++) { + Array offsets(m_players.size()); + for (int pl = 1; pl <= m_players.size(); pl++) { offsets[pl] = size; size *= m_players[pl]->NumStrategies(); } Array newResults(size); - for (int i = 1; i <= newResults.Length(); newResults[i++] = 0) + for (int i = 1; i <= newResults.size(); newResults[i++] = 0) ; for (auto iter : StrategyContingencies(StrategySupportProfile(const_cast(this)))) { long newindex = 1L; - for (int pl = 1; pl <= m_players.Length(); pl++) { + for (int pl = 1; pl <= m_players.size(); pl++) { if (iter->GetStrategy(pl)->m_offset < 0) { // This is a contingency involving a new strategy... skip newindex = -1L; diff --git a/src/games/gametree.cc b/src/games/gametree.cc index 1bb9e7ecc..67ab404ca 100644 --- a/src/games/gametree.cc +++ b/src/games/gametree.cc @@ -129,14 +129,15 @@ void GameTreeActionRep::DeleteAction() m_infoset->GetGame()->IncrementVersion(); int where; - for (where = 1; where <= m_infoset->m_actions.Length() && m_infoset->m_actions[where] != this; + for (where = 1; where <= m_infoset->m_actions.size() && m_infoset->m_actions[where] != this; where++) ; m_infoset->RemoveAction(where); for (auto member : m_infoset->m_members) { member->children[where]->DeleteTree(); - member->children.Remove(where)->Invalidate(); + member->children[where]->Invalidate(); + erase_atindex(member->children, where); } if (m_infoset->IsChanceInfoset()) { @@ -165,8 +166,8 @@ GameTreeInfosetRep::GameTreeInfosetRep(GameTreeRep *p_efg, int p_number, GamePla 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++) { + auto prob = lexical_cast(Rational(1, m_actions.size())); + for (int act = 1; act <= m_actions.size(); act++) { m_probs[act] = prob; } } @@ -230,24 +231,25 @@ GameAction GameTreeInfosetRep::InsertAction(GameAction p_action /* =0 */) } m_efg->IncrementVersion(); - int where = m_actions.Length() + 1; + int where = m_actions.size() + 1; if (p_action) { for (where = 1; m_actions[where] != p_action; where++) ; } auto *action = new GameTreeActionRep(where, "", this); - m_actions.Insert(action, where); + m_actions.insert(std::next(m_actions.cbegin(), where - 1), action); if (m_player->IsChance()) { - m_probs.Insert(Number("0"), where); + m_probs.insert(std::next(m_probs.cbegin(), where - 1), Number("0")); } - for (int act = 1; act <= m_actions.Length(); act++) { + for (int act = 1; act <= m_actions.size(); act++) { m_actions[act]->m_number = act; } - for (int i = 1; i <= m_members.Length(); i++) { - m_members[i]->children.Insert(new GameTreeNodeRep(m_efg, m_members[i]), where); + for (int i = 1; i <= m_members.size(); i++) { + m_members[i]->children.insert(std::next(m_members[i]->children.cbegin(), where - 1), + new GameTreeNodeRep(m_efg, m_members[i])); } m_efg->ClearComputedValues(); @@ -258,11 +260,12 @@ GameAction GameTreeInfosetRep::InsertAction(GameAction p_action /* =0 */) void GameTreeInfosetRep::RemoveAction(int which) { m_efg->IncrementVersion(); - m_actions.Remove(which)->Invalidate(); + m_actions[which]->Invalidate(); + erase_atindex(m_actions, which); if (m_player->IsChance()) { - m_probs.Remove(which); + erase_atindex(m_probs, which); } - for (; which <= m_actions.Length(); which++) { + for (; which <= m_actions.size(); which++) { m_actions[which]->m_number = which; } } @@ -294,7 +297,7 @@ 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++) { + for (int m = 1; m <= members.size(); m++) { if (action->Precedes(members[m])) { if (!newiset) { newiset = members[m]->LeaveInfoset(); @@ -466,7 +469,7 @@ void GameTreeNodeRep::DeleteTree() while (!children.empty()) { children.front()->DeleteTree(); children.front()->Invalidate(); - children.Remove(1); + erase_atindex(children, 1); } if (infoset) { infoset->RemoveMember(this); @@ -488,9 +491,9 @@ void GameTreeNodeRep::CopySubtree(GameTreeNodeRep *src, GameTreeNodeRep *stop) return; } - if (src->children.Length()) { + if (src->children.size()) { AppendMove(src->infoset); - for (int i = 1; i <= src->children.Length(); i++) { + for (int i = 1; i <= src->children.size(); i++) { children[i]->CopySubtree(src->children[i], stop); } } @@ -513,7 +516,7 @@ void GameTreeNodeRep::CopyTree(GameNode p_src) if (!src->children.empty()) { AppendMove(src->infoset); - for (int i = 1; i <= src->children.Length(); i++) { + for (int i = 1; i <= src->children.size(); i++) { children[i]->CopySubtree(src->children[i], this); } @@ -558,7 +561,7 @@ void GameTreeNodeRep::SetInfoset(GameInfoset p_infoset) if (!infoset || infoset == p_infoset) { return; } - if (p_infoset->NumActions() != children.Length()) { + if (p_infoset->NumActions() != children.size()) { throw DimensionException(); } m_efg->IncrementVersion(); @@ -578,16 +581,15 @@ GameInfoset GameTreeNodeRep::LeaveInfoset() m_efg->IncrementVersion(); GameTreeInfosetRep *oldInfoset = infoset; - if (oldInfoset->m_members.Length() == 1) { + 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 = new GameTreeInfosetRep(m_efg, player->m_infosets.size() + 1, player, children.size()); infoset->AddMember(this); - for (int i = 1; i <= oldInfoset->m_actions.Length(); i++) { + for (int i = 1; i <= oldInfoset->m_actions.size(); i++) { infoset->m_actions[i]->SetLabel(oldInfoset->m_actions[i]->GetLabel()); } @@ -642,7 +644,7 @@ GameInfoset GameTreeNodeRep::InsertMove(GamePlayer p_player, int p_actions) m_efg->IncrementVersion(); return InsertMove( - new GameTreeInfosetRep(m_efg, p_player->m_infosets.Length() + 1, p_player, p_actions)); + new GameTreeInfosetRep(m_efg, p_player->m_infosets.size() + 1, p_player, p_actions)); } GameInfoset GameTreeNodeRep::InsertMove(GameInfoset p_infoset) @@ -808,7 +810,7 @@ bool GameTreeRep::IsPerfectRecall(GameInfoset &s1, GameInfoset &s2) const void GameTreeRep::NumberNodes(GameTreeNodeRep *n, int &index) { n->number = index++; - for (int child = 1; child <= n->children.Length(); NumberNodes(n->children[child++], index)) + for (int child = 1; child <= n->children.size(); NumberNodes(n->children[child++], index)) ; } @@ -820,16 +822,16 @@ void GameTreeRep::Canonicalize() int nodeindex = 1; NumberNodes(m_root, nodeindex); - for (int pl = 0; pl <= m_players.Length(); pl++) { + for (int pl = 0; pl <= m_players.size(); pl++) { GamePlayerRep *player = (pl) ? m_players[pl] : 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++) { + for (int iset = 1; iset <= player->m_infosets.size(); 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++) { + for (int i = 1; i < infoset->m_members.size(); i++) { + for (int j = 1; j < infoset->m_members.size() - i; j++) { if (infoset->m_members[j + 1]->number < infoset->m_members[j]->number) { GameTreeNodeRep *tmp = infoset->m_members[j]; infoset->m_members[j] = infoset->m_members[j + 1]; @@ -842,12 +844,12 @@ 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()) + for (int i = 1; i < player->m_infosets.size(); i++) { + for (int j = 1; j < player->m_infosets.size() - i; j++) { + int a = ((player->m_infosets[j + 1]->m_members.size()) ? player->m_infosets[j + 1]->m_members[1]->number : 0); - int b = ((player->m_infosets[j]->m_members.Length()) + int b = ((player->m_infosets[j]->m_members.size()) ? player->m_infosets[j]->m_members[1]->number : 0); @@ -860,7 +862,7 @@ void GameTreeRep::Canonicalize() } // Reassign information set IDs - for (int iset = 1; iset <= player->m_infosets.Length(); iset++) { + for (int iset = 1; iset <= player->m_infosets.size(); iset++) { player->m_infosets[iset]->m_number = iset; } } @@ -982,7 +984,7 @@ GamePlayer GameTreeRep::NewPlayer() GamePlayerRep *player = nullptr; player = new GamePlayerRep(this, m_players.size() + 1); m_players.push_back(player); - for (int outc = 1; outc <= m_outcomes.Last(); outc++) { + for (int outc = 1; outc <= m_outcomes.last_index(); outc++) { m_outcomes[outc]->m_payoffs.push_back(Number()); } ClearComputedValues(); @@ -1022,8 +1024,8 @@ Array GameTreeRep::GetInfosets() const Array GameTreeRep::NumInfosets() const { - Array foo(m_players.Length()); - for (int i = 1; i <= foo.Length(); i++) { + Array foo(m_players.size()); + for (int i = 1; i <= foo.size(); i++) { foo[i] = m_players[i]->NumInfosets(); } return foo; diff --git a/src/gui/analysis.cc b/src/gui/analysis.cc index 0924582db..0a2fbf3b0 100644 --- a/src/gui/analysis.cc +++ b/src/gui/analysis.cc @@ -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(); } } @@ -199,14 +199,14 @@ template void gbtAnalysisProfileList::Load(TiXmlNode *p_analysis) 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 = 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(); } } } diff --git a/src/gui/app.cc b/src/gui/app.cc index 5c4744377..adb3c31bf 100644 --- a/src/gui/app.cc +++ b/src/gui/app.cc @@ -70,7 +70,7 @@ 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. @@ -131,7 +131,7 @@ void gbtApplication::SetCurrentDir(const wxString &p_dir) bool gbtApplication::AreDocumentsModified() const { - for (int i = 1; i <= m_documents.Length(); i++) { + for (int i = 1; i <= m_documents.size(); i++) { if (m_documents[i]->IsModified()) { return true; } diff --git a/src/gui/dleditnode.cc b/src/gui/dleditnode.cc index 926fc39b4..a25a8b87d 100644 --- a/src/gui/dleditnode.cc +++ b/src/gui/dleditnode.cc @@ -57,7 +57,7 @@ dialogEditNode::dialogEditNode(wxWindow *p_parent, const Gambit::GameNode &p_nod 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(); } } } @@ -73,7 +73,7 @@ dialogEditNode::dialogEditNode(wxWindow *p_parent, const Gambit::GameNode &p_nod m_infosetList.push_back(infoset); m_infoset->Append(wxString::Format(_("Player %d, Infoset %d"), pl, iset)); if (infoset == p_node->GetInfoset()) { - selection = m_infosetList.Length(); + selection = m_infosetList.size(); } } } diff --git a/src/gui/dlefglayout.cc b/src/gui/dlefglayout.cc index 17d4f22e0..5d6417243 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 4d763d5fc..99e16a3c2 100644 --- a/src/gui/dlefglogit.cc +++ b/src/gui/dlefglogit.cc @@ -184,8 +184,8 @@ void gbtLogitBehavList::AddProfile(const wxString &p_text, bool p_forceShow) } 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)); } diff --git a/src/gui/dlnfglogit.cc b/src/gui/dlnfglogit.cc index 1da4fc182..0a067c3ce 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]; } diff --git a/src/gui/efgpanel.cc b/src/gui/efgpanel.cc index 711b27c2f..bc69ab340 100644 --- a/src/gui/efgpanel.cc +++ b/src/gui/efgpanel.cc @@ -582,19 +582,20 @@ 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++) { + for (int pl = 1; pl <= m_playerPanels.size(); pl++) { m_playerPanels[pl]->OnUpdate(); } @@ -603,7 +604,7 @@ void gbtTreePlayerToolbar::OnUpdate() void gbtTreePlayerToolbar::PostPendingChanges() { - for (int pl = 1; pl <= m_playerPanels.Length(); pl++) { + for (int pl = 1; pl <= m_playerPanels.size(); pl++) { m_playerPanels[pl]->PostPendingChanges(); } } diff --git a/src/gui/gamedoc.cc b/src/gui/gamedoc.cc index 9a7350ef4..940d3c01a 100644 --- a/src/gui/gamedoc.cc +++ b/src/gui/gamedoc.cc @@ -44,7 +44,7 @@ gbtBehavDominanceStack::gbtBehavDominanceStack(gbtGameDocument *p_doc, bool p_st gbtBehavDominanceStack::~gbtBehavDominanceStack() { - for (int i = 1; i <= m_supports.Length(); delete m_supports[i++]) + for (int i = 1; i <= m_supports.size(); delete m_supports[i++]) ; } @@ -58,7 +58,7 @@ void gbtBehavDominanceStack::SetStrict(bool p_strict) void gbtBehavDominanceStack::Reset() { - for (int i = 1; i <= m_supports.Length(); delete m_supports[i++]) + for (int i = 1; i <= m_supports.size(); delete m_supports[i++]) ; m_supports = Gambit::Array(); if (m_doc->IsTree()) { @@ -70,7 +70,7 @@ void gbtBehavDominanceStack::Reset() bool gbtBehavDominanceStack::NextLevel() { - if (m_current < m_supports.Length()) { + if (m_current < m_supports.size()) { m_current++; return true; } @@ -115,7 +115,7 @@ gbtStrategyDominanceStack::gbtStrategyDominanceStack(gbtGameDocument *p_doc, boo gbtStrategyDominanceStack::~gbtStrategyDominanceStack() { - for (int i = 1; i <= m_supports.Length(); delete m_supports[i++]) + for (int i = 1; i <= m_supports.size(); delete m_supports[i++]) ; } @@ -129,7 +129,7 @@ void gbtStrategyDominanceStack::SetStrict(bool p_strict) void gbtStrategyDominanceStack::Reset() { - for (int i = 1; i <= m_supports.Length(); delete m_supports[i++]) + for (int i = 1; i <= m_supports.size(); delete m_supports[i++]) ; m_supports = Gambit::Array(); m_supports.push_back(new Gambit::StrategySupportProfile(m_doc->GetGame())); @@ -139,7 +139,7 @@ void gbtStrategyDominanceStack::Reset() bool gbtStrategyDominanceStack::NextLevel() { - if (m_current < m_supports.Length()) { + if (m_current < m_supports.size()) { m_current++; return true; } @@ -272,7 +272,7 @@ bool gbtGameDocument::LoadDocument(const wxString &p_filename, bool p_saveUndo) } } - m_currentProfileList = m_profiles.Length(); + m_currentProfileList = m_profiles.size(); TiXmlNode *colors = docroot->FirstChild("colors"); if (colors) { @@ -335,7 +335,7 @@ void gbtGameDocument::SaveDocument(std::ostream &p_file) const p_file << "\n"; } - for (int i = 1; i <= m_profiles.Length(); i++) { + for (int i = 1; i <= m_profiles.size(); i++) { m_profiles[i]->Save(p_file); } @@ -363,19 +363,20 @@ 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); + while (!m_profiles.empty()) { + delete m_profiles.back(); + m_profiles.pop_back(); } m_currentProfileList = 0; } - for (int i = 1; i <= m_views.Length(); m_views[i++]->OnUpdate()) + for (int i = 1; i <= m_views.size(); m_views[i++]->OnUpdate()) ; } void gbtGameDocument::PostPendingChanges() { - for (int i = 1; i <= m_views.Length(); m_views[i++]->PostPendingChanges()) + for (int i = 1; i <= m_views.size(); m_views[i++]->PostPendingChanges()) ; } @@ -383,7 +384,7 @@ void gbtGameDocument::BuildNfg() { if (m_game->IsTree()) { m_stratSupports.Reset(); - for (int i = 1; i <= m_profiles.Length(); m_profiles[i++]->BuildNfg()) + for (int i = 1; i <= m_profiles.size(); m_profiles[i++]->BuildNfg()) ; } } @@ -410,8 +411,9 @@ void gbtGameDocument::Undo() m_game = nullptr; - while (m_profiles.Length() > 0) { - delete m_profiles.Remove(1); + while (!m_profiles.empty()) { + delete m_profiles.back(); + m_profiles.pop_back(); } m_currentProfileList = 0; @@ -423,7 +425,7 @@ void gbtGameDocument::Undo() LoadDocument(tempfile, false); wxRemoveFile(tempfile); - for (int i = 1; i <= m_views.Length(); m_views[i++]->OnUpdate()) + for (int i = 1; i <= m_views.size(); m_views[i++]->OnUpdate()) ; } @@ -434,8 +436,9 @@ void gbtGameDocument::Redo() m_game = nullptr; - while (m_profiles.Length() > 0) { - delete m_profiles.Remove(1); + while (!m_profiles.empty()) { + delete m_profiles.back(); + m_profiles.pop_back(); } m_currentProfileList = 0; @@ -447,7 +450,7 @@ void gbtGameDocument::Redo() LoadDocument(tempfile, false); wxRemoveFile(tempfile); - for (int i = 1; i <= m_views.Length(); m_views[i++]->OnUpdate()) + for (int i = 1; i <= m_views.size(); m_views[i++]->OnUpdate()) ; } @@ -460,7 +463,7 @@ void gbtGameDocument::SetCurrentProfile(int p_profile) void gbtGameDocument::AddProfileList(gbtAnalysisOutput *p_profs) { m_profiles.push_back(p_profs); - m_currentProfileList = m_profiles.Length(); + m_currentProfileList = m_profiles.size(); UpdateViews(GBT_DOC_MODIFIED_VIEWS); } diff --git a/src/gui/gamedoc.h b/src/gui/gamedoc.h index 1f6ae07ed..2448bf7a4 100644 --- a/src/gui/gamedoc.h +++ b/src/gui/gamedoc.h @@ -51,7 +51,7 @@ class gbtBehavDominanceStack { //! //! 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 @@ -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); } }; //! @@ -122,7 +122,7 @@ class gbtStrategyDominanceStack { //! //! 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 @@ -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); } }; // @@ -288,12 +288,12 @@ class gbtGameDocument { const gbtAnalysisOutput &GetProfiles(int p_index) const { return *m_profiles[p_index]; } void AddProfileList(gbtAnalysisOutput *); 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); /* diff --git a/src/gui/gameframe.cc b/src/gui/gameframe.cc index 942803dc7..bfa9edfcb 100644 --- a/src/gui/gameframe.cc +++ b/src/gui/gameframe.cc @@ -1009,7 +1009,7 @@ void gbtGameFrame::OnEditReveal(wxCommandEvent &) if (dialog.ShowModal() == wxID_OK) { try { - for (int pl = 1; pl <= dialog.GetPlayers().Length(); pl++) { + for (int pl = 1; pl <= dialog.GetPlayers().size(); pl++) { m_doc->DoRevealAction(m_doc->GetSelectNode()->GetInfoset(), dialog.GetPlayers()[pl]); } } diff --git a/src/gui/nfgpanel.cc b/src/gui/nfgpanel.cc index 10d61b1c8..72ef550eb 100644 --- a/src/gui/nfgpanel.cc +++ b/src/gui/nfgpanel.cc @@ -278,19 +278,20 @@ 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++) { + for (int pl = 1; pl <= m_playerPanels.size(); pl++) { m_playerPanels[pl]->OnUpdate(); } @@ -299,7 +300,7 @@ void gbtTablePlayerToolbar::OnUpdate() void gbtTablePlayerToolbar::PostPendingChanges() { - for (int pl = 1; pl <= m_playerPanels.Length(); pl++) { + for (int pl = 1; pl <= m_playerPanels.size(); pl++) { m_playerPanels[pl]->PostPendingChanges(); } } diff --git a/src/gui/nfgtable.cc b/src/gui/nfgtable.cc index aaa1fd885..52b07b315 100644 --- a/src/gui/nfgtable.cc +++ b/src/gui/nfgtable.cc @@ -997,23 +997,23 @@ void gbtTableWidget::OnBeginEdit(wxSheetEvent &) { m_doc->PostPendingChanges(); void gbtTableWidget::OnUpdate() { - if (m_doc->NumPlayers() > m_rowPlayers.Length() + m_colPlayers.Length()) { + if (m_doc->NumPlayers() > m_rowPlayers.size() + m_colPlayers.size()) { for (int 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++) { + else if (m_doc->NumPlayers() < m_rowPlayers.size() + m_colPlayers.size()) { + for (int i = 1; i <= m_rowPlayers.size(); i++) { if (m_rowPlayers[i] > m_doc->NumPlayers()) { - m_rowPlayers.Remove(i--); + erase_atindex(m_rowPlayers, i--); } } - for (int i = 1; i <= m_colPlayers.Length(); i++) { + for (int i = 1; i <= m_colPlayers.size(); i++) { if (m_colPlayers[i] > m_doc->NumPlayers()) { - m_colPlayers.Remove(i--); + erase_atindex(m_colPlayers, i--); } } } @@ -1060,20 +1060,19 @@ bool gbtTableWidget::ShowDominance() const { return m_nfgPanel->IsDominanceShown void gbtTableWidget::SetRowPlayer(int index, int pl) { - if (contains(m_rowPlayers, 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 (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(); } @@ -1108,20 +1107,19 @@ int gbtTableWidget::RowToStrategy(int player, int row) const void gbtTableWidget::SetColPlayer(int index, int pl) { - if (contains(m_colPlayers, 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 (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(); } diff --git a/src/gui/nfgtable.h b/src/gui/nfgtable.h index 079f4adc2..b9aecb1fe 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/style.cc b/src/gui/style.cc index 0711b54ed..cb219bdfb 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 > 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 (int 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 (int pl = 1; pl <= m_playerColors.size(); pl++) { s << " &firstedge, Array &edgelist) } numco = 0; - for (newedge = 1; newedge <= edgelist.Length(); newedge++) { + for (newedge = 1; newedge <= edgelist.size(); newedge++) { i = edgelist[newedge].node1; j = edgelist[newedge].node2; diff --git a/src/solvers/enummixed/enummixed.cc b/src/solvers/enummixed/enummixed.cc index 6c48b50c8..9a568aa88 100644 --- a/src/solvers/enummixed/enummixed.cc +++ b/src/solvers/enummixed/enummixed.cc @@ -54,15 +54,15 @@ List>> EnumMixedStrategySolution::GetCliques() c List>> solution; for (size_t cl = 1; cl <= m_cliques1.size(); cl++) { solution.push_back(List>()); - for (int i = 1; i <= m_cliques1[cl].Length(); i++) { - for (int j = 1; j <= m_cliques2[cl].Length(); j++) { + for (int i = 1; i <= m_cliques1[cl].size(); i++) { + for (int j = 1; j <= m_cliques2[cl].size(); j++) { MixedStrategyProfile profile(m_game->NewMixedStrategyProfile(static_cast(0))); - for (int k = 1; k <= m_key1[m_cliques1[cl][i]].Length(); k++) { + for (int k = 1; k <= m_key1[m_cliques1[cl][i]].size(); k++) { profile[k] = m_key1[m_cliques1[cl][i]][k]; } - for (int k = 1; k <= m_key2[m_cliques2[cl][j]].Length(); k++) { - profile[k + m_key1[m_cliques1[cl][i]].Length()] = m_key2[m_cliques2[cl][j]][k]; + for (int k = 1; k <= m_key2[m_cliques2[cl][j]].size(); k++) { + profile[k + m_key1[m_cliques1[cl][i]].size()] = m_key2[m_cliques2[cl][j]][k]; } solution[cl].push_back(profile); } diff --git a/src/solvers/enumpoly/behavextend.cc b/src/solvers/enumpoly/behavextend.cc index 769091a3d..139d38df8 100644 --- a/src/solvers/enumpoly/behavextend.cc +++ b/src/solvers/enumpoly/behavextend.cc @@ -112,7 +112,7 @@ std::list DeviationSupports(const BehaviorSupportProfile std::list answer; Array active_act_no(isetlist.size()); - for (int k = 1; k <= active_act_no.Length(); k++) { + for (int k = 1; k <= active_act_no.size(); k++) { active_act_no[k] = 0; } diff --git a/src/solvers/enumpoly/gpartltr.imp b/src/solvers/enumpoly/gpartltr.imp index d4be575f2..3d7f244f5 100644 --- a/src/solvers/enumpoly/gpartltr.imp +++ b/src/solvers/enumpoly/gpartltr.imp @@ -102,7 +102,7 @@ T TreeOfPartials::MaximalNonconstantContributionRECURSIVE( increment = -increment; } - for (int j = 1; j <= p.Length(); j++) { + for (int j = 1; j <= p.size(); j++) { for (int k = 1; k <= wrtos[j]; k++) { increment *= halvesoflengths[j]; increment /= (T)k; @@ -138,7 +138,7 @@ T TreeOfPartials::MaximalNonconstantDifferenceRECURSIVE( increment = -increment; } - for (int j = 1; j <= p.Length(); j++) { + for (int j = 1; j <= p.size(); j++) { for (int k = 1; k <= wrtos[j]; k++) { increment *= halvesoflengths[j]; increment /= (T)k; @@ -164,7 +164,7 @@ T TreeOfPartials::MaximalNonconstantDifferenceRECURSIVE( increment = -increment; } - for (int j = 1; j <= p.Length(); j++) { + for (int j = 1; j <= p.size(); j++) { for (int k = 1; k <= wrtos[j]; k++) { increment *= halvesoflengths[j]; increment /= (T)k; @@ -189,7 +189,7 @@ T TreeOfPartials::MaximalNonconstantDifferenceRECURSIVE( increment = -increment; } - for (int j = 1; j <= p.Length(); j++) { + for (int j = 1; j <= p.size(); j++) { for (int k = 1; k <= wrtos[j]; k++) { increment *= halvesoflengths[j]; increment /= (T)k; @@ -211,8 +211,8 @@ template T TreeOfPartials::MaximalNonconstantContribution(const Gambit::Vector &p, const Gambit::Vector &halvesoflengths) const { - Gambit::Vector WithRespectTos(p.Length()); - for (int i = 1; i <= p.Length(); i++) { + Gambit::Vector WithRespectTos(p.size()); + for (int i = 1; i <= p.size(); i++) { WithRespectTos[i] = 0; } @@ -224,8 +224,8 @@ T TreeOfPartials::MaximalNonconstantDifference(const TreeOfPartials &other const Gambit::Vector &p, const Gambit::Vector &halvesoflengths) const { - Gambit::Vector WithRespectTos(p.Length()); - for (int i = 1; i <= p.Length(); i++) { + Gambit::Vector WithRespectTos(p.size()); + for (int i = 1; i <= p.size(); i++) { WithRespectTos[i] = 0; } diff --git a/src/solvers/enumpoly/odometer.cc b/src/solvers/enumpoly/odometer.cc index 369d9b6b6..fadb7cd23 100644 --- a/src/solvers/enumpoly/odometer.cc +++ b/src/solvers/enumpoly/odometer.cc @@ -32,8 +32,8 @@ //--------------------------- gIndexOdometer::gIndexOdometer(const Gambit::Array &IndexUpperBounds) - : MinIndices(IndexUpperBounds.Length()), MaxIndices(IndexUpperBounds), - CurIndices(IndexUpperBounds.Length()) + : MinIndices(IndexUpperBounds.size()), MaxIndices(IndexUpperBounds), + CurIndices(IndexUpperBounds.size()) { int i; for (i = 1; i <= NoIndices(); i++) { @@ -47,8 +47,7 @@ gIndexOdometer::gIndexOdometer(const Gambit::Array &IndexUpperBounds) gIndexOdometer::gIndexOdometer(const Gambit::Array &IndexLowerBounds, const Gambit::Array &IndexUpperBounds) - : MinIndices(IndexLowerBounds), MaxIndices(IndexUpperBounds), - CurIndices(IndexUpperBounds.Length()) + : MinIndices(IndexLowerBounds), MaxIndices(IndexUpperBounds), CurIndices(IndexUpperBounds.size()) { CurIndices[1] = MinIndices[1] - 1; for (int i = 2; i <= NoIndices(); i++) { diff --git a/src/solvers/enumpoly/odometer.h b/src/solvers/enumpoly/odometer.h index 277bc6d08..e6554f156 100644 --- a/src/solvers/enumpoly/odometer.h +++ b/src/solvers/enumpoly/odometer.h @@ -69,7 +69,7 @@ class gIndexOdometer { bool Turn(); // Information - int NoIndices() const { return MaxIndices.Length(); } + int NoIndices() const { return MaxIndices.size(); } }; #endif // ODOMETER_H diff --git a/src/solvers/enumpoly/quiksolv.imp b/src/solvers/enumpoly/quiksolv.imp index 7010616c8..b300312cf 100644 --- a/src/solvers/enumpoly/quiksolv.imp +++ b/src/solvers/enumpoly/quiksolv.imp @@ -135,7 +135,7 @@ static bool fuzzy_equals(double x, double y) static bool fuzzy_equals(const Gambit::Vector &x, const Gambit::Vector &y) { - for (int i = x.First(); i <= x.Last(); i++) { + for (int i = x.first_index(); i <= x.last_index(); i++) { if (!fuzzy_equals(x[i], y[i])) { return false; } @@ -378,7 +378,7 @@ Gambit::Vector QuikSolv::SlowNewtonPolishOnce(const Gambit::Vector::gRectangle(const Gambit::Vector &lower_bd, const Gambit::Vecto : sides() { // assert (lower_bd.Check(upper_bd)); - for (int i = 1; i <= upper_bd.Length(); i++) { + for (int i = 1; i <= upper_bd.size(); i++) { gInterval side(lower_bd[i], upper_bd[i]); sides.push_back(side); } diff --git a/src/solvers/liap/efgliap.cc b/src/solvers/liap/efgliap.cc index 827910cad..3e3660a35 100644 --- a/src/solvers/liap/efgliap.cc +++ b/src/solvers/liap/efgliap.cc @@ -115,7 +115,7 @@ bool AgentLyapunovFunction::Gradient(const Vector &x, Vector &gr { const double DELTA = .00001; m_profile = x; - for (int i = 1; i <= x.Length(); i++) { + for (int i = 1; i <= x.size(); i++) { m_profile[i] += DELTA; double value = PenalizedLiapValue(m_profile); m_profile[i] -= 2.0 * DELTA; diff --git a/src/solvers/linalg/basis.cc b/src/solvers/linalg/basis.cc index 3b930c10f..cf3833e58 100644 --- a/src/solvers/linalg/basis.cc +++ b/src/solvers/linalg/basis.cc @@ -38,12 +38,12 @@ Basis::Basis(int first, int last, int firstlabel, int lastlabel) colBlocked(firstlabel, lastlabel), rowBlocked(first, last) { int i; - for (i = cols.First(); i <= cols.Last(); i++) { + for (i = cols.first_index(); i <= cols.last_index(); i++) { cols[i] = 0; colBlocked[i] = false; } - for (i = basis.First(); i <= basis.Last(); i++) { + for (i = basis.first_index(); i <= basis.last_index(); i++) { basis[i] = -i; slacks[i] = i; rowBlocked[i] = false; @@ -74,17 +74,23 @@ Basis &Basis::operator=(const Basis &orig) // Public Members // ------------------------- -int Basis::First() const { return basis.First(); } +int Basis::First() const { return basis.first_index(); } -int Basis::Last() const { return basis.Last(); } +int Basis::Last() const { return basis.last_index(); } -int Basis::MinCol() const { return cols.First(); } +int Basis::MinCol() const { return cols.first_index(); } -int Basis::MaxCol() const { return cols.Last(); } +int Basis::MaxCol() const { return cols.last_index(); } -bool Basis::IsRegColumn(int col) const { return col >= cols.First() && col <= cols.Last(); } +bool Basis::IsRegColumn(int col) const +{ + return col >= cols.first_index() && col <= cols.last_index(); +} -bool Basis::IsSlackColumn(int col) const { return -col >= basis.First() && -col <= basis.Last(); } +bool Basis::IsSlackColumn(int col) const +{ + return -col >= basis.first_index() && -col <= basis.last_index(); +} int Basis::Pivot(int outindex, int col) { @@ -188,7 +194,7 @@ void Basis::CheckBasis() { bool check = true; - for (int i = basis.First(); i <= basis.Last() && check; i++) { + for (int i = basis.first_index(); i <= basis.last_index() && check; i++) { if (basis[i] != -i) { check = false; } diff --git a/src/solvers/linalg/btableau.imp b/src/solvers/linalg/btableau.imp index 47ab7a3a5..c7258af10 100644 --- a/src/solvers/linalg/btableau.imp +++ b/src/solvers/linalg/btableau.imp @@ -73,14 +73,14 @@ TableauInterface::TableauInterface(const Gambit::Matrix &A, const Gambit:: template TableauInterface::TableauInterface(const Gambit::Matrix &A, const Gambit::Array &art, const Gambit::Vector &b) - : A(&A), b(&b), basis(A.MinRow(), A.MaxRow(), A.MinCol(), A.MaxCol() + art.Length()), + : A(&A), b(&b), basis(A.MinRow(), A.MaxRow(), A.MinCol(), A.MaxCol() + art.size()), solution(A.MinRow(), A.MaxRow()), npivots(0), - artificial(A.MaxCol() + 1, A.MaxCol() + art.Length()) + artificial(A.MaxCol() + 1, A.MaxCol() + art.size()) { Gambit::linalg::epsilon(eps1, 5); Gambit::linalg::epsilon(eps2); - for (int i = 0; i < art.Length(); i++) { - artificial[A.MaxCol() + 1 + i] = art[art.First() + i]; + for (int i = 0; i < art.size(); i++) { + artificial[A.MaxCol() + 1 + i] = art[art.first_index() + i]; } } @@ -203,7 +203,7 @@ template T TableauInterface::Epsilon(int i) const template bool TableauInterface::IsArtifColumn(int col) const { - return (col >= artificial.First() && col <= artificial.Last()); + return (col >= artificial.first_index() && col <= artificial.last_index()); } } // namespace linalg diff --git a/src/solvers/linalg/lemketab.imp b/src/solvers/linalg/lemketab.imp index b61c45fdc..ee386dbc2 100644 --- a/src/solvers/linalg/lemketab.imp +++ b/src/solvers/linalg/lemketab.imp @@ -87,7 +87,7 @@ template int LemkeTableau::SF_ExitIndex(int inlabel) BestSet.push_back(i); } } - if (BestSet.Length() == 0) { + if (BestSet.size() == 0) { //* gout << "\nBestSet.Length() == 0, Find(0): " << Find(0); return 0; } @@ -97,7 +97,7 @@ template int LemkeTableau::SF_ExitIndex(int inlabel) && incol[Find(0)]<=eps2 && incol[Find(0)] >= (-eps2) ) return Find(0); */ - if (BestSet.Length() <= 0) { + if (BestSet.size() <= 0) { throw BadExitIndex(); } @@ -109,7 +109,7 @@ template int LemkeTableau::SF_ExitIndex(int inlabel) this->BasisVector(col); // gout << "\nLength = " << BestSet.Length(); //* gout << "\n x = " << col << "\n"; - while (BestSet.Length() > 1) { + while (BestSet.size() > 1) { if (c > this->MaxRow()) { throw BadExitIndex(); } @@ -120,7 +120,7 @@ template int LemkeTableau::SF_ExitIndex(int inlabel) // Initialize tempmax. tempmax = col[BestSet[1]] / incol[BestSet[1]]; // Find the maximum ratio. - for (i = 2; i <= BestSet.Length(); i++) { + for (i = 2; i <= BestSet.size(); i++) { ratio = col[BestSet[i]] / incol[BestSet[i]]; //* if (ratio > tempmax) tempmax = ratio; if (ratio < tempmax) { @@ -130,11 +130,11 @@ template int LemkeTableau::SF_ExitIndex(int inlabel) // if (tempmax <= (T 2)*eps1) throw BadExitIndex(); // Remove nonmaximizers from the list of candidate columns. - for (i = BestSet.Length(); i >= 1; i--) { + for (i = BestSet.size(); i >= 1; i--) { ratio = col[BestSet[i]] / incol[BestSet[i]]; //* if (ratio < tempmax -eps1) if (ratio > tempmax + this->eps2) { - BestSet.Remove(i); + erase_atindex(BestSet, i); } } // else { @@ -143,10 +143,10 @@ template int LemkeTableau::SF_ExitIndex(int inlabel) // } c++; } - if (BestSet.Length() <= 0) { + if (BestSet.empty()) { throw BadExitIndex(); } - return BestSet[1]; + return BestSet.front(); } // @@ -173,14 +173,14 @@ template int LemkeTableau::ExitIndex(int inlabel) } } // Is this really needed? - if (BestSet.Length() == 0) { + if (BestSet.size() == 0) { // gout << "\nBestSet.Length() == 0, Find(0):\n" << Find(0); - if (BestSet.Length() == 0 && incol[this->Find(0)] <= this->eps2 && + if (BestSet.size() == 0 && incol[this->Find(0)] <= this->eps2 && incol[this->Find(0)] >= (-this->eps2)) { return this->Find(0); } } - if (BestSet.Length() <= 0) { + if (BestSet.size() <= 0) { throw BadExitIndex(); } @@ -192,7 +192,7 @@ template int LemkeTableau::ExitIndex(int inlabel) this->BasisVector(col); // gout << "\nLength = " << BestSet.Length(); // gout << "\n x = " << col << "\n"; - while (BestSet.Length() > 1) { + while (BestSet.size() > 1) { // this is where ITEM 001 is failing if (c > this->MaxRow()) { throw BadExitIndex(); @@ -204,7 +204,7 @@ template int LemkeTableau::ExitIndex(int inlabel) // Initialize tempmax. tempmax = col[BestSet[1]] / incol[BestSet[1]]; // Find the maximum ratio. - for (i = 2; i <= BestSet.Length(); i++) { + for (i = 2; i <= BestSet.size(); i++) { ratio = col[BestSet[i]] / incol[BestSet[i]]; if (ratio > tempmax) { tempmax = ratio; @@ -213,10 +213,10 @@ template int LemkeTableau::ExitIndex(int inlabel) // if(tempmax <= (T 2)*eps1) throw BadExitIndex(); // Remove nonmaximizers from the list of candidate columns. - for (i = BestSet.Length(); i >= 1; i--) { + for (i = BestSet.size(); i >= 1; i--) { ratio = col[BestSet[i]] / incol[BestSet[i]]; if (ratio < tempmax - this->eps1) { - BestSet.Remove(i); + erase_atindex(BestSet, i); } } // else { @@ -225,10 +225,10 @@ template int LemkeTableau::ExitIndex(int inlabel) // } c++; } - if (BestSet.Length() <= 0) { + if (BestSet.empty()) { throw BadExitIndex(); } - return BestSet[1]; + return BestSet.front(); } // diff --git a/src/solvers/linalg/lhtab.imp b/src/solvers/linalg/lhtab.imp index 3f2cbea8d..9572eafc2 100644 --- a/src/solvers/linalg/lhtab.imp +++ b/src/solvers/linalg/lhtab.imp @@ -33,8 +33,9 @@ namespace linalg { template LHTableau::LHTableau(const Matrix &A1, const Matrix &A2, const Vector &b1, const Vector &b2) - : T1(A1, b1), T2(A2, b2), tmp1(b1.First(), b1.Last()), tmp2(b2.First(), b2.Last()), - solution(b1.First(), b2.Last()) + : T1(A1, b1), T2(A2, b2), tmp1(b1.first_index(), b1.last_index()), tmp2(b2.first_index(), + b2.last_index()), + solution(b1.first_index(), b2.last_index()) { } @@ -107,10 +108,10 @@ template Gambit::linalg::BFS LHTableau::GetBFS() { T1.BasisVector(tmp1); T2.BasisVector(tmp2); - for (int i = tmp1.First(); i <= tmp1.Last(); i++) { + for (int i = tmp1.first_index(); i <= tmp1.last_index(); i++) { solution[i] = tmp1[i]; } - for (int i = tmp2.First(); i <= tmp2.Last(); i++) { + for (int i = tmp2.first_index(); i <= tmp2.last_index(); i++) { solution[i] = tmp2[i]; } Gambit::linalg::BFS cbfs; diff --git a/src/solvers/linalg/lpsolve.imp b/src/solvers/linalg/lpsolve.imp index edb647932..aed8ee785 100644 --- a/src/solvers/linalg/lpsolve.imp +++ b/src/solvers/linalg/lpsolve.imp @@ -28,10 +28,10 @@ namespace linalg { template LPSolve::LPSolve(const Matrix &A, const Vector &b, const Vector &c, int nequals) - : well_formed(true), feasible(true), bounded(true), nvars(c.Length()), neqns(b.Length()), + : well_formed(true), feasible(true), bounded(true), nvars(c.size()), neqns(b.size()), nequals(nequals), total_cost(0), tmin(0), tab(A, Artificials(b), b), UB(nullptr), LB(nullptr), - ub(nullptr), lb(nullptr), xx(nullptr), cost(nullptr), y(b.Length()), x(b.Length()), - d(b.Length()) + ub(nullptr), lb(nullptr), xx(nullptr), cost(nullptr), y(b.size()), x(b.size()), + d(b.size()) { // These are the values recommended by Murtagh (1981) for 15 digit // accuracy in LP problems @@ -40,7 +40,7 @@ LPSolve::LPSolve(const Matrix &A, const Vector &b, const Vector &c, Gambit::linalg::epsilon(eps3, 6); // Check dimensions - if (A.NumRows() != b.Length() || A.NumColumns() != c.Length()) { + if (A.NumRows() != b.size() || A.NumColumns() != c.size()) { well_formed = false; return; } @@ -49,7 +49,7 @@ LPSolve::LPSolve(const Matrix &A, const Vector &b, const Vector &c, int i, j, num_inequals, xlab, num_artific; num_inequals = A.NumRows() - nequals; - num_artific = Artificials(b).Length(); + num_artific = Artificials(b).size(); nvars += num_artific; UB = new Array(nvars + neqns); @@ -59,7 +59,7 @@ LPSolve::LPSolve(const Matrix &A, const Vector &b, const Vector &c, xx = new Vector(nvars + neqns); cost = new Vector(nvars + neqns); - for (j = (*UB).First(); j <= (*UB).Last(); j++) { + for (j = (*UB).first_index(); j <= (*UB).last_index(); j++) { (*UB)[j] = false; (*LB)[j] = false; (*ub)[j] = (T)0; @@ -97,7 +97,7 @@ LPSolve::LPSolve(const Matrix &A, const Vector &b, const Vector &c, tab.SetCost(*cost); // set xx to be initial feasible solution to phase II - for (i = 1; i <= (*xx).Length(); i++) { + for (i = 1; i <= (*xx).size(); i++) { if ((*LB)[i]) { (*xx)[i] = (*lb)[i]; } @@ -109,7 +109,7 @@ LPSolve::LPSolve(const Matrix &A, const Vector &b, const Vector &c, } } tab.BasisVector(x); - for (i = 1; i <= x.Length(); i++) { + for (i = 1; i <= x.size(); i++) { xlab = tab.Label(i); if (xlab < 0) { xlab = nvars - xlab; @@ -140,10 +140,10 @@ LPSolve::LPSolve(const Matrix &A, const Vector &b, const Vector &c, // install Phase II unit cost vector - for (i = c.First(); i <= c.Last(); i++) { + for (i = c.first_index(); i <= c.last_index(); i++) { (*cost)[i] = c[i]; } - for (i = c.Last() + 1; i <= nvars + neqns; i++) { + for (i = c.last_index() + 1; i <= nvars + neqns; i++) { (*cost)[i] = (T)0; } @@ -165,7 +165,7 @@ LPSolve::LPSolve(const Matrix &A, const Vector &b, const Vector &c, template Array LPSolve::Artificials(const Vector &b) { Array ret; - for (int i = b.First(); i <= b.Last(); i++) { + for (int i = b.first_index(); i <= b.last_index(); i++) { if (b[i] < (T)0) { ret.push_back(i); } @@ -198,7 +198,7 @@ template void LPSolve::Solve(int phase) outlab = tab.Label(out); } // update xx - for (i = 1; i <= x.Length(); i++) { + for (i = 1; i <= x.size(); i++) { xlab = tab.Label(i); if (xlab < 0) { xlab = nvars - xlab; diff --git a/src/solvers/linalg/lptab.h b/src/solvers/linalg/lptab.h index c7f16e6db..0edadee38 100644 --- a/src/solvers/linalg/lptab.h +++ b/src/solvers/linalg/lptab.h @@ -58,7 +58,7 @@ template class LPTableau : public Tableau { BFS DualBFS() const; // returns the label of the index of the last artificial variable - int GetLastLabel() { return this->artificial.Last(); } + int GetLastLabel() { return this->artificial.last_index(); } // select Basis elements according to Tableau rows and cols void BasisSelect(const Array &rowv, Vector &colv) const; diff --git a/src/solvers/linalg/lptab.imp b/src/solvers/linalg/lptab.imp index 58c379300..52d7935e9 100644 --- a/src/solvers/linalg/lptab.imp +++ b/src/solvers/linalg/lptab.imp @@ -38,7 +38,7 @@ LPTableau::LPTableau(const Matrix &A, const Vector &b) template LPTableau::LPTableau(const Matrix &A, const Array &art, const Vector &b) : Tableau(A, art, b), dual(A.MinRow(), A.MaxRow()), unitcost(A.MinRow(), A.MaxRow()), - cost(A.MinCol(), A.MaxCol() + art.Length()) + cost(A.MinCol(), A.MaxCol() + art.size()) { } @@ -46,21 +46,21 @@ LPTableau::LPTableau(const Matrix &A, const Array &art, const Vector< template <> void LPTableau::SetCost(const Vector &c) { - if (cost.First() == c.First() && cost.Last() == c.Last()) { + if (cost.first_index() == c.first_index() && cost.last_index() == c.last_index()) { cost = c; cost *= Rational(Tableau::TotDenom()); - for (int i = unitcost.First(); i <= unitcost.Last(); i++) { + for (int i = unitcost.first_index(); i <= unitcost.last_index(); i++) { unitcost[i] = Rational(0); } Refactor(); SolveDual(); return; } - for (int i = c.First(); i <= cost.Last(); i++) { + for (int i = c.first_index(); i <= cost.last_index(); i++) { cost[i] = c[i] * Rational(Tableau::TotDenom()); } - for (int i = unitcost.First(); i <= unitcost.Last(); i++) { - unitcost[i] = c[cost.size() + i - unitcost.First() + 1]; + for (int i = unitcost.first_index(); i <= unitcost.last_index(); i++) { + unitcost[i] = c[cost.size() + i - unitcost.first_index() + 1]; } Refactor(); SolveDual(); @@ -68,24 +68,24 @@ template <> void LPTableau::SetCost(const Vector &c) template <> void LPTableau::SetCost(const Vector &c) { - if (cost.First() == c.First() && cost.Last() == c.Last()) { + if (cost.first_index() == c.first_index() && cost.last_index() == c.last_index()) { cost = c; unitcost = 0.0; Refactor(); SolveDual(); return; } - if (c.First() != cost.First()) { + if (c.first_index() != cost.first_index()) { throw DimensionException(); } - if (c.Last() != (cost.Last() + unitcost.Length())) { + if (c.last_index() != (cost.last_index() + unitcost.size())) { throw DimensionException(); } - for (int i = c.First(); i <= cost.Last(); i++) { + for (int i = c.first_index(); i <= cost.last_index(); i++) { cost[i] = c[i]; } - for (int i = unitcost.First(); i <= unitcost.Last(); i++) { - unitcost[i] = c[cost.size() + i - unitcost.First() + 1]; + for (int i = unitcost.first_index(); i <= unitcost.last_index(); i++) { + unitcost[i] = c[cost.size() + i - unitcost.first_index() + 1]; } Refactor(); SolveDual(); @@ -104,7 +104,7 @@ template <> Rational LPTableau::TotalCost() const Vector sol(MinRow(), MaxRow()); BasisSelect(unitcost, cost, tmpcol); BasisVector(sol); - for (int i = tmpcol.First(); i <= tmpcol.Last(); i++) { + for (int i = tmpcol.first_index(); i <= tmpcol.last_index(); i++) { if (Label(i) > 0) { tmpcol[i] /= (Rational)Tableau::TotDenom(); } @@ -180,7 +180,7 @@ template std::list> LPTableau::ReversePivots() for (int i = best_set.size(); i >= 1; i--) { x = solution[best_set[i]] / tmpcol[best_set[i]]; if (this->LtZero(x - ratio)) { - best_set.Remove(i); + erase_atindex(best_set, i); } } @@ -190,7 +190,7 @@ template std::list> LPTableau::ReversePivots() for (int i = best_set.size(); i >= 1; i--) { a_ij = static_cast(1) / tmpcol[best_set[i]]; if (this->LeZero(a_ij)) { - best_set.Remove(i); + erase_atindex(best_set, i); } else { // next check that prior pivot entry attains max ratio @@ -198,17 +198,17 @@ template std::list> LPTableau::ReversePivots() ratio = b_i / a_ij; flag = false; - for (k = tmpcol.First(); k <= tmpcol.Last() && !flag; k++) { + for (k = tmpcol.first_index(); k <= tmpcol.last_index() && !flag; k++) { if (k != best_set[i]) { a_ik = -a_ij * tmpcol[k]; b_k = solution[k] - b_i * tmpcol[k]; if (this->GtZero(a_ik) && this->GtZero(b_k / a_ik - ratio)) { - best_set.Remove(i); + erase_atindex(best_set, i); flag = true; } else if (this->GtZero(a_ik) && this->EqZero(b_k / a_ik - ratio) && this->Label(k) < j) { - best_set.Remove(i); + erase_atindex(best_set, i); flag = true; } } @@ -227,7 +227,7 @@ template std::list> LPTableau::ReversePivots() a_ij = tmpdual * tmpcol; c_j = RelativeCost(j); if (this->EqZero(a_ij)) { - best_set.Remove(i); + erase_atindex(best_set, i); } else { ratio = c_j / a_ij; @@ -241,11 +241,11 @@ template std::list> LPTableau::ReversePivots() c_k = RelativeCost(enter); c_jo = c_k - a_ik * ratio; if (this->GeZero(c_jo)) { - best_set.Remove(i); + erase_atindex(best_set, i); } else { flag = false; - for (k = -this->b->Last(); k < enter && !flag; k++) { + for (k = -this->b->last_index(); k < enter && !flag; k++) { if (k != 0) { if (k < 0) { a_ik = tmpdual[-k]; @@ -258,7 +258,7 @@ template std::list> LPTableau::ReversePivots() c_jo = c_k - a_ik * ratio; if (this->LtZero(c_jo)) { - best_set.Remove(i); + erase_atindex(best_set, i); flag = true; } } @@ -300,7 +300,7 @@ template bool LPTableau::IsDualReversePivot(int i, int j) } ratio = c_j / a_ij; - for (int k = -this->b->Last(); k <= cost.Last(); k++) { + for (int k = -this->b->last_index(); k <= cost.last_index(); k++) { if (k != 0) { if (k < 0) { a_ik = tmpdual[-k]; @@ -336,7 +336,7 @@ template bool LPTableau::IsDualReversePivot(int i, int j) } ratio = c_k / a_ik; - for (int k = -this->b->Last(); k <= cost.Last(); k++) { + for (int k = -this->b->last_index(); k <= cost.last_index(); k++) { if (k != 0) { if (k < 0) { a_ik = tmpdual[-k]; @@ -367,7 +367,7 @@ template bool LPTableau::IsDualReversePivot(int i, int j) return false; } - for (int k = this->b->First(); k <= this->b->Last(); k++) { + for (int k = this->b->first_index(); k <= this->b->last_index(); k++) { if (k != i) { b_k = solution[k] - b_i * tmpcol[k]; if (this->GtZero(b_k) && this->Label(k) < j) { diff --git a/src/solvers/linalg/ludecomp.imp b/src/solvers/linalg/ludecomp.imp index 1543ad449..b6e480960 100644 --- a/src/solvers/linalg/ludecomp.imp +++ b/src/solvers/linalg/ludecomp.imp @@ -124,10 +124,10 @@ template void LUDecomposition::refactor() template void LUDecomposition::solveT(const Vector &c, Vector &y) const { - if (c.First() != y.First() || c.Last() != y.Last()) { + if (c.first_index() != y.first_index() || c.last_index() != y.last_index()) { throw DimensionException(); } - if (c.First() != basis.First() || c.Last() != basis.Last()) { + if (c.first_index() != basis.First() || c.last_index() != basis.Last()) { throw DimensionException(); } @@ -146,10 +146,10 @@ template void LUDecomposition::solveT(const Vector &c, Vector template void LUDecomposition::solve(const Vector &a, Vector &d) const { - if (a.First() != d.First() || a.Last() != d.Last()) { + if (a.first_index() != d.first_index() || a.last_index() != d.last_index()) { throw DimensionException(); } - if (a.First() != basis.First() || a.Last() != basis.Last()) { + if (a.first_index() != basis.First() || a.last_index() != basis.Last()) { throw DimensionException(); } @@ -245,9 +245,9 @@ template void LUDecomposition::VectorEtaSolve(const EtaMatrix &eta, Vector &y) const { Vector v = y; - for (int i = v.First(); i <= v.Last(); i++) { + for (int i = v.first_index(); i <= v.last_index(); i++) { if (i == eta.col) { - for (int j = v.First(); j <= v.Last(); j++) { + for (int j = v.first_index(); j <= v.last_index(); j++) { if (j != eta.col) { y[i] -= v[j] * eta.etadata[j]; } @@ -278,7 +278,7 @@ void LUDecomposition::EtaVectorSolve(const EtaMatrix &eta, Vector &d) cons Vector v = d; T temp = v[eta.col] / eta.etadata[eta.col]; - for (int i = v.First(); i <= v.Last(); i++) { + for (int i = v.first_index(); i <= v.last_index(); i++) { if (i == eta.col) { d[i] = temp; } @@ -300,15 +300,15 @@ template void LUDecomposition::yLP_Trans(Vector &y) const template void LUDecomposition::yLP_mult(const Vector &y, int j, Vector &ans) const { - int ell = j + y.First(); + int ell = j + y.first_index(); - for (int i = y.First(); i <= y.Last(); i++) { + for (int i = y.first_index(); i <= y.last_index(); i++) { if (i != L[j].second.col) { ans[i] = y[i]; } else { T temp = static_cast(0); - for (int k = ans.First(); k <= ans.Last(); k++) { + for (int k = ans.first_index(); k <= ans.last_index(); k++) { temp += y[k] * L[j].second.etadata[k]; } ans[i] = temp; @@ -328,9 +328,9 @@ template void LUDecomposition::LPd_Trans(Vector &d) const template void LUDecomposition::LPd_mult(Vector &d, int j, Vector &ans) const { - int k = j + d.First(); + int k = j + d.first_index(); std::swap(d[k], d[L[j].first]); - for (int i = d.First(); i <= d.Last(); i++) { + for (int i = d.first_index(); i <= d.last_index(); i++) { if (i == L[j].second.col) { ans[i] = d[i] * L[j].second.etadata[i]; } diff --git a/src/solvers/linalg/tableau.cc b/src/solvers/linalg/tableau.cc index 3bb107643..6f65dde41 100644 --- a/src/solvers/linalg/tableau.cc +++ b/src/solvers/linalg/tableau.cc @@ -33,13 +33,13 @@ namespace linalg { // Constructors and Destructor Tableau::Tableau(const Matrix &A, const Vector &b) - : TableauInterface(A, b), B(*this), tmpcol(b.First(), b.Last()) + : TableauInterface(A, b), B(*this), tmpcol(b.first_index(), b.last_index()) { Solve(b, solution); } Tableau::Tableau(const Matrix &A, const Array &art, const Vector &b) - : TableauInterface(A, art, b), B(*this), tmpcol(b.First(), b.Last()) + : TableauInterface(A, art, b), B(*this), tmpcol(b.first_index(), b.last_index()) { Solve(b, solution); } @@ -119,7 +119,7 @@ void Tableau::SetRefactor(int n) { B.SetRefactor(n); } void Tableau::SetConst(const Vector &bnew) { - if (bnew.First() != b->First() || bnew.Last() != b->Last()) { + if (bnew.first_index() != b->first_index() || bnew.last_index() != b->last_index()) { throw DimensionException(); } b = &bnew; @@ -147,7 +147,7 @@ bool Tableau::IsFeasible() { //** is it really necessary to solve first here? Solve(*b, solution); - for (int i = solution.First(); i <= solution.Last(); i++) { + for (int i = solution.first_index(); i <= solution.last_index(); i++) { if (solution[i] >= eps2) { return false; } @@ -191,7 +191,7 @@ Integer find_lcd(const Matrix &mat) Integer find_lcd(const Vector &vec) { Integer lcd(1); - for (int i = vec.First(); i <= vec.Last(); i++) { + for (int i = vec.first_index(); i <= vec.last_index(); i++) { lcd = lcm(vec[i].denominator(), lcd); } return lcd; @@ -201,7 +201,7 @@ Integer find_lcd(const Vector &vec) Tableau::Tableau(const Matrix &A, const Vector &b) : TableauInterface(A, b), Tabdat(A.MinRow(), A.MaxRow(), A.MinCol(), A.MaxCol()), - Coeff(b.First(), b.Last()), denom(1), tmpcol(b.First(), b.Last()), + Coeff(b.first_index(), b.last_index()), denom(1), tmpcol(b.first_index(), b.last_index()), nonbasic(A.MinCol(), A.MaxCol()) { for (int j = MinCol(); j <= MaxCol(); j++) { @@ -213,7 +213,7 @@ Tableau::Tableau(const Matrix &A, const Vector &b) throw BadDenom(); } - for (int i = b.First(); i <= b.Last(); i++) { + for (int i = b.first_index(); i <= b.last_index(); i++) { Rational x = b[i] * (Rational)totdenom; if (x.denominator() != 1) { throw BadDenom(); @@ -229,7 +229,7 @@ Tableau::Tableau(const Matrix &A, const Vector &b) Tabdat(i, j) = x.numerator(); } } - for (int i = b.First(); i <= b.Last(); i++) { + for (int i = b.first_index(); i <= b.last_index(); i++) { solution[i] = (Rational)Coeff[i]; } } @@ -237,9 +237,9 @@ Tableau::Tableau(const Matrix &A, const Vector &b) Tableau::Tableau(const Matrix &A, const Array &art, const Vector &b) : TableauInterface(A, art, b), - Tabdat(A.MinRow(), A.MaxRow(), A.MinCol(), A.MaxCol() + art.Length()), - Coeff(b.First(), b.Last()), denom(1), tmpcol(b.First(), b.Last()), - nonbasic(A.MinCol(), A.MaxCol() + art.Length()) + Tabdat(A.MinRow(), A.MaxRow(), A.MinCol(), A.MaxCol() + art.size()), + Coeff(b.first_index(), b.last_index()), denom(1), tmpcol(b.first_index(), b.last_index()), + nonbasic(A.MinCol(), A.MaxCol() + art.size()) { for (int j = MinCol(); j <= MaxCol(); j++) { nonbasic[j] = j; @@ -250,7 +250,7 @@ Tableau::Tableau(const Matrix &A, const Array &art, throw BadDenom(); } - for (int i = b.First(); i <= b.Last(); i++) { + for (int i = b.first_index(); i <= b.last_index(); i++) { Rational x = b[i] * (Rational)totdenom; if (x.denominator() != 1) { throw BadDenom(); @@ -269,7 +269,7 @@ Tableau::Tableau(const Matrix &A, const Array &art, Tabdat(artificial[j], j) = totdenom; } } - for (int i = b.First(); i <= b.Last(); i++) { + for (int i = b.first_index(); i <= b.last_index(); i++) { solution[i] = (Rational)Coeff[i]; } } @@ -298,11 +298,11 @@ Tableau &Tableau::operator=(const Tableau &orig) int Tableau::remap(int col_index) const { - int i = nonbasic.First(); - while (i <= nonbasic.Last() && nonbasic[i] != col_index) { + int i = nonbasic.first_index(); + while (i <= nonbasic.last_index() && nonbasic[i] != col_index) { i++; } - if (i > nonbasic.Last()) { + if (i > nonbasic.last_index()) { throw DimensionException(); } return i; @@ -310,7 +310,7 @@ int Tableau::remap(int col_index) const Matrix Tableau::GetInverse() { - Vector mytmpcol(tmpcol.First(), tmpcol.Last()); + Vector mytmpcol(tmpcol.first_index(), tmpcol.last_index()); Matrix inv(MinRow(), MaxRow(), MinRow(), MaxRow()); for (int i = inv.MinCol(); i <= inv.MaxCol(); i++) { MySolveColumn(-i, mytmpcol); @@ -401,7 +401,7 @@ void Tableau::Pivot(int outrow, int in_col) basis.Pivot(outrow, in_col); nonbasic[col] = outlabel; - for (i = solution.First(); i <= solution.Last(); i++) { + for (i = solution.first_index(); i <= solution.last_index(); i++) { //** solution[i] = (Rational)(Coeff[i])/(Rational)(denom*totdenom); solution[i] = Rational(Coeff[i] * sign(denom * totdenom)); } @@ -417,7 +417,7 @@ void Tableau::Pivot(int outrow, int in_col) void Tableau::SolveColumn(int in_col, Vector &out) { - Vector tempcol(tmpcol.First(), tmpcol.Last()); + Vector tempcol(tmpcol.first_index(), tmpcol.last_index()); if (Member(in_col)) { out = (Rational)0; out[Find(in_col)] = Rational(abs(denom)); @@ -425,7 +425,7 @@ void Tableau::SolveColumn(int in_col, Vector &out) else { int col = remap(in_col); Tabdat.GetColumn(col, tempcol); - for (int i = tempcol.First(); i <= tempcol.Last(); i++) { + for (int i = tempcol.first_index(); i <= tempcol.last_index(); i++) { out[i] = (Rational)(tempcol[i]) * (Rational)(sign(denom * totdenom)); } } @@ -433,7 +433,7 @@ void Tableau::SolveColumn(int in_col, Vector &out) if (in_col < 0) { out *= Rational(totdenom); } - for (int i = out.First(); i <= out.Last(); i++) { + for (int i = out.first_index(); i <= out.last_index(); i++) { if (Label(i) < 0) { out[i] = (Rational)out[i] / (Rational)totdenom; } @@ -442,7 +442,7 @@ void Tableau::SolveColumn(int in_col, Vector &out) void Tableau::MySolveColumn(int in_col, Vector &out) { - Vector tempcol(tmpcol.First(), tmpcol.Last()); + Vector tempcol(tmpcol.first_index(), tmpcol.last_index()); if (Member(in_col)) { out = (Rational)0; out[Find(in_col)] = Rational(abs(denom)); @@ -450,7 +450,7 @@ void Tableau::MySolveColumn(int in_col, Vector &out) else { int col = remap(in_col); Tabdat.GetColumn(col, tempcol); - for (int i = tempcol.First(); i <= tempcol.Last(); i++) { + for (int i = tempcol.first_index(); i <= tempcol.last_index(); i++) { out[i] = (Rational)(tempcol[i]) * (Rational)(sign(denom * totdenom)); } } @@ -479,7 +479,7 @@ void Tableau::Refactor() int i, j; Matrix inv(GetInverse()); Matrix Tabnew(Tabdat.MinRow(), Tabdat.MaxRow(), Tabdat.MinCol(), Tabdat.MaxCol()); - for (i = nonbasic.First(); i <= nonbasic.Last(); i++) { + for (i = nonbasic.first_index(); i <= nonbasic.last_index(); i++) { GetColumn(nonbasic[i], mytmpcol); // if(nonbasic[i]>=0) mytmpcol*=Rational(totdenom); Tabnew.SetColumn(i, inv * mytmpcol * (Rational)sign(denom * totdenom)); @@ -490,7 +490,7 @@ void Tableau::Refactor() // gout << "\nTabdat:\n" << Tabdat; // gout << "\nTabnew:\n" << Tabnew; - Vector Coeffnew(Coeff.First(), Coeff.Last()); + Vector Coeffnew(Coeff.first_index(), Coeff.last_index()); Coeffnew = inv * (*b) * Rational(totdenom) * Rational(sign(denom * totdenom)); // gout << "\nCoeff:\n" << Coeff; @@ -544,7 +544,7 @@ void Tableau::SolveT(const Vector &c, Vector &y) bool Tableau::IsFeasible() { - for (int i = solution.First(); i <= solution.Last(); i++) { + for (int i = solution.first_index(); i <= solution.last_index(); i++) { if (solution[i] >= eps2) { return false; } @@ -574,7 +574,7 @@ void Tableau::BasisVector(Vector &out) const { out = solution; out = out / (Rational)abs(denom); - for (int i = out.First(); i <= out.Last(); i++) { + for (int i = out.first_index(); i <= out.last_index(); i++) { if (Label(i) < 0) { out[i] = out[i] / (Rational)totdenom; } diff --git a/src/solvers/linalg/vertenum.imp b/src/solvers/linalg/vertenum.imp index 1a1eec5aa..6e6c50475 100644 --- a/src/solvers/linalg/vertenum.imp +++ b/src/solvers/linalg/vertenum.imp @@ -76,9 +76,9 @@ template void VertexEnumerator::DualSearch(LPTableau &tab) if (mult_opt) { tab.SetConst(btemp); // install artificial constraint vector LPTableau tab2(tab); - for (int i = b.First(); i <= b.Last(); i++) { + for (int i = b.first_index(); i <= b.last_index(); i++) { if (b[i] == static_cast(0)) { - for (int j = -b.Last(); j <= A.MaxCol(); j++) { + for (int j = -b.last_index(); j <= A.MaxCol(); j++) { if (j && !tab.Member(j) && !tab.IsBlocked(j)) { if (tab.IsDualReversePivot(i, j)) { branches[depth] += 1; diff --git a/src/solvers/logit/efglogit.cc b/src/solvers/logit/efglogit.cc index 16c0cd734..122aa43c1 100644 --- a/src/solvers/logit/efglogit.cc +++ b/src/solvers/logit/efglogit.cc @@ -33,7 +33,7 @@ namespace { double LogLike(const Vector &p_frequencies, const Vector &p_point) { double logL = 0.0; - for (int i = 1; i <= p_frequencies.Length(); i++) { + for (int i = 1; i <= p_frequencies.size(); i++) { logL += p_frequencies[i] * log(p_point[i]); } return logL; @@ -42,7 +42,7 @@ double LogLike(const Vector &p_frequencies, const Vector &p_poin double DiffLogLike(const Vector &p_frequencies, const Vector &p_tangent) { double diff_logL = 0.0; - for (int i = 1; i <= p_frequencies.Length(); i++) { + for (int i = 1; i <= p_frequencies.size(); i++) { diff_logL += p_frequencies[i] * p_tangent[i]; } return diff_logL; @@ -51,7 +51,7 @@ double DiffLogLike(const Vector &p_frequencies, const Vector &p_ MixedBehaviorProfile PointToProfile(const Game &p_game, const Vector &p_point) { MixedBehaviorProfile profile(p_game); - for (int i = 1; i < p_point.Length(); i++) { + for (int i = 1; i < p_point.size(); i++) { profile[i] = exp(p_point[i]); } return profile; @@ -231,7 +231,7 @@ void EquationSystem::GetValue(const Vector &p_point, Vector &p_l { LogBehavProfile profile(PointToLogProfile(m_game, p_point)); double lambda = p_point.back(); - for (int i = 1; i <= p_lhs.Length(); i++) { + for (int i = 1; i <= p_lhs.size(); i++) { p_lhs[i] = m_equations[i]->Value(profile, lambda); } } @@ -241,8 +241,8 @@ void EquationSystem::GetJacobian(const Vector &p_point, Matrix & LogBehavProfile profile(PointToLogProfile(m_game, p_point)); double lambda = p_point.back(); - for (int i = 1; i <= m_equations.Length(); i++) { - Vector column(p_point.Length()); + for (int i = 1; i <= m_equations.size(); i++) { + Vector column(p_point.size()); m_equations[i]->Gradient(profile, lambda, column); p_matrix.SetColumn(i, column); } diff --git a/src/solvers/logit/nfglogit.cc b/src/solvers/logit/nfglogit.cc index 5cebb77d4..c7a9a05aa 100644 --- a/src/solvers/logit/nfglogit.cc +++ b/src/solvers/logit/nfglogit.cc @@ -34,7 +34,7 @@ namespace { MixedStrategyProfile PointToProfile(const Game &p_game, const Vector &p_point) { MixedStrategyProfile profile(p_game->NewMixedStrategyProfile(0.0)); - for (int i = 1; i < p_point.Length(); i++) { + for (int i = 1; i < p_point.size(); i++) { profile[i] = exp(p_point[i]); } return profile; @@ -53,7 +53,7 @@ Vector ProfileToPoint(const LogitQREMixedStrategyProfile &p_profile) double LogLike(const Vector &p_frequencies, const Vector &p_point) { double logL = 0.0; - for (int i = 1; i <= p_frequencies.Length(); i++) { + for (int i = 1; i <= p_frequencies.size(); i++) { logL += p_frequencies[i] * log(p_point[i]); } return logL; @@ -62,7 +62,7 @@ double LogLike(const Vector &p_frequencies, const Vector &p_poin double DiffLogLike(const Vector &p_frequencies, const Vector &p_tangent) { double diff_logL = 0.0; - for (int i = 1; i <= p_frequencies.Length(); i++) { + for (int i = 1; i <= p_frequencies.size(); i++) { diff_logL += p_frequencies[i] * p_tangent[i]; } return diff_logL; diff --git a/src/solvers/logit/path.cc b/src/solvers/logit/path.cc index fca7adf1f..b431e45ea 100644 --- a/src/solvers/logit/path.cc +++ b/src/solvers/logit/path.cc @@ -138,12 +138,12 @@ void PathTracer::TracePath( double pert = 0.0; // The current version of the perturbation being applied double pert_countdown = 0.0; // How much longer (in arclength) to apply perturbation - Vector u(x.Length()); + Vector u(x.size()); // t is current tangent at x; newT is tangent at u, which is the next point. - Vector t(x.Length()), newT(x.Length()); - Vector y(x.Length() - 1); - Matrix b(x.Length(), x.Length() - 1); - SquareMatrix q(x.Length()); + Vector t(x.size()), newT(x.size()); + Vector y(x.size() - 1); + Matrix b(x.size(), x.size() - 1); + SquareMatrix q(x.size()); p_jacobian(x, b); QRDecomp(b, q); @@ -158,7 +158,7 @@ void PathTracer::TracePath( } // Predictor step - for (int k = 1; k <= x.Length(); k++) { + for (int k = 1; k <= x.size(); k++) { u[k] = x[k] + h * p_omega * t[k]; } diff --git a/src/solvers/simpdiv/simpdiv.cc b/src/solvers/simpdiv/simpdiv.cc index 7ce370b5a..6523a0712 100644 --- a/src/solvers/simpdiv/simpdiv.cc +++ b/src/solvers/simpdiv/simpdiv.cc @@ -488,7 +488,7 @@ Rational NashSimpdivStrategySolver::State::getlabel(MixedStrategyProfile &vec) { Integer lcd(1); - for (int i = vec.First(); i <= vec.Last(); i++) { + for (int i = vec.first_index(); i <= vec.last_index(); i++) { lcd = lcm(vec[i].denominator(), lcd); } return lcd; From dff978e95d1393c76def38d55f336aa4e2f910d8 Mon Sep 17 00:00:00 2001 From: Ted Turocy Date: Mon, 23 Dec 2024 13:32:27 +0000 Subject: [PATCH 48/99] Simplify and modernise implementation of interval and rectangle. --- Makefile.am | 5 +- src/core/list.h | 1 + src/solvers/enumpoly/behavextend.cc | 6 +- src/solvers/enumpoly/efgpoly.cc | 2 +- src/solvers/enumpoly/gpartltr.h | 10 +- src/solvers/enumpoly/gpartltr.imp | 17 +- src/solvers/enumpoly/ineqsolv.h | 8 +- src/solvers/enumpoly/ineqsolv.imp | 11 +- src/solvers/enumpoly/interval.h | 89 -------- src/solvers/enumpoly/nfgpoly.cc | 2 +- src/solvers/enumpoly/quiksolv.h | 16 +- src/solvers/enumpoly/quiksolv.imp | 47 ++--- src/solvers/enumpoly/rectangl.cc | 26 --- src/solvers/enumpoly/rectangl.h | 84 -------- src/solvers/enumpoly/rectangl.imp | 307 ---------------------------- src/solvers/enumpoly/rectangle.h | 180 ++++++++++++++++ src/solvers/linalg/lhtab.imp | 5 +- src/solvers/linalg/lpsolve.imp | 3 +- 18 files changed, 242 insertions(+), 577 deletions(-) delete mode 100644 src/solvers/enumpoly/interval.h delete mode 100644 src/solvers/enumpoly/rectangl.cc delete mode 100644 src/solvers/enumpoly/rectangl.h delete mode 100644 src/solvers/enumpoly/rectangl.imp create mode 100644 src/solvers/enumpoly/rectangle.h diff --git a/Makefile.am b/Makefile.am index aa2b81be0..aef9f187b 100644 --- a/Makefile.am +++ b/Makefile.am @@ -414,13 +414,10 @@ gambit_enumpoly_SOURCES = \ src/solvers/enumpoly/ineqsolv.cc \ src/solvers/enumpoly/ineqsolv.h \ src/solvers/enumpoly/ineqsolv.imp \ - src/solvers/enumpoly/interval.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/rectangle.h \ src/solvers/enumpoly/behavextend.cc \ src/solvers/enumpoly/behavextend.h \ src/solvers/enumpoly/gcomplex.cc \ diff --git a/src/core/list.h b/src/core/list.h index 1281e7e3e..cc4822e8a 100644 --- a/src/core/list.h +++ b/src/core/list.h @@ -49,6 +49,7 @@ template class List { public: 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; List() = default; diff --git a/src/solvers/enumpoly/behavextend.cc b/src/solvers/enumpoly/behavextend.cc index 139d38df8..6e30d7d90 100644 --- a/src/solvers/enumpoly/behavextend.cc +++ b/src/solvers/enumpoly/behavextend.cc @@ -23,7 +23,7 @@ #include "behavextend.h" #include "gpoly.h" #include "gpolylst.h" -#include "rectangl.h" +#include "rectangle.h" #include "ineqsolv.h" using namespace Gambit; @@ -317,7 +317,7 @@ bool ExtendsToNash(const MixedBehaviorProfile &p_solution, // Set up the test and do it Vector sample(num_vars); - return IneqSolv(inequalities).ASolutionExists(gRectangle(bottoms, tops), sample); + return IneqSolv(inequalities).ASolutionExists(Rectangle(bottoms, tops), sample); } } // namespace Nash @@ -458,7 +458,7 @@ bool ExtendsToAgentNash(const MixedBehaviorProfile &p_solution, // Set up the test and do it Vector sample(num_vars); - return IneqSolv(inequalities).ASolutionExists(gRectangle(bottoms, tops), sample); + return IneqSolv(inequalities).ASolutionExists(Rectangle(bottoms, tops), sample); } } // namespace Nash diff --git a/src/solvers/enumpoly/efgpoly.cc b/src/solvers/enumpoly/efgpoly.cc index dada00e73..ed603fd6b 100644 --- a/src/solvers/enumpoly/efgpoly.cc +++ b/src/solvers/enumpoly/efgpoly.cc @@ -27,7 +27,7 @@ #include "gameseq.h" #include "gpoly.h" #include "gpolylst.h" -#include "rectangl.h" +#include "rectangle.h" #include "quiksolv.h" #include "behavextend.h" diff --git a/src/solvers/enumpoly/gpartltr.h b/src/solvers/enumpoly/gpartltr.h index 6c9ad5046..c2272823e 100644 --- a/src/solvers/enumpoly/gpartltr.h +++ b/src/solvers/enumpoly/gpartltr.h @@ -25,7 +25,7 @@ #include "gambit.h" #include "gtree.h" -#include "rectangl.h" +#include "rectangle.h" #include "gpoly.h" #include "gpolylst.h" @@ -71,10 +71,10 @@ template class TreeOfPartials { T ValueOfRootPoly(const Gambit::Vector &point) const; T ValueOfPartialOfRootPoly(const int &, const Gambit::Vector &) const; Gambit::Vector VectorOfPartials(const Gambit::Vector &) const; - bool PolyHasNoRootsIn(const gRectangle &) const; - bool MultiaffinePolyHasNoRootsIn(const gRectangle &) const; - bool PolyEverywhereNegativeIn(const gRectangle &) const; - bool MultiaffinePolyEverywhereNegativeIn(const gRectangle &) const; + bool PolyHasNoRootsIn(const Rectangle &) const; + bool MultiaffinePolyHasNoRootsIn(const Rectangle &) const; + bool PolyEverywhereNegativeIn(const Rectangle &) const; + bool MultiaffinePolyEverywhereNegativeIn(const Rectangle &) const; // friend gOutput& operator << (gOutput& output, const TreeOfPartials& x); }; diff --git a/src/solvers/enumpoly/gpartltr.imp b/src/solvers/enumpoly/gpartltr.imp index 3d7f244f5..c42f12224 100644 --- a/src/solvers/enumpoly/gpartltr.imp +++ b/src/solvers/enumpoly/gpartltr.imp @@ -243,7 +243,7 @@ template T TreeOfPartials::ValueOfRootPoly(const Gambit::Vector return RootPoly().Evaluate(point); } -template bool TreeOfPartials::PolyHasNoRootsIn(const gRectangle &r) const +template bool TreeOfPartials::PolyHasNoRootsIn(const Rectangle &r) const { if (this->RootNode()->GetData().IsMultiaffine()) { return MultiaffinePolyHasNoRootsIn(r); @@ -272,8 +272,7 @@ template bool TreeOfPartials::PolyHasNoRootsIn(const gRectangle } } -template -bool TreeOfPartials::MultiaffinePolyHasNoRootsIn(const gRectangle &r) const +template bool TreeOfPartials::MultiaffinePolyHasNoRootsIn(const Rectangle &r) const { int sign; if (this->RootNode()->GetData().Evaluate(r.Center()) > (T)0) { @@ -301,10 +300,10 @@ bool TreeOfPartials::MultiaffinePolyHasNoRootsIn(const gRectangle &r) cons Gambit::Vector point(Dmnsn()); for (int i = 1; i <= Dmnsn(); i++) { if (topbottoms[i] == 0) { - point[i] = r.LowerBoundOfCoord(i); + point[i] = r.Side(i).LowerBound(); } else { - point[i] = r.UpperBoundOfCoord(i); + point[i] = r.Side(i).UpperBound(); } } if ((T)sign * this->RootNode()->GetData().Evaluate(point) <= (T)0) { @@ -316,7 +315,7 @@ bool TreeOfPartials::MultiaffinePolyHasNoRootsIn(const gRectangle &r) cons } template -bool TreeOfPartials::MultiaffinePolyEverywhereNegativeIn(const gRectangle &r) const +bool TreeOfPartials::MultiaffinePolyEverywhereNegativeIn(const Rectangle &r) const { if (Dmnsn() == 0) { Gambit::Vector point(Dmnsn()); @@ -345,10 +344,10 @@ bool TreeOfPartials::MultiaffinePolyEverywhereNegativeIn(const gRectangle Gambit::Vector point(Dmnsn()); for (int i = 1; i <= Dmnsn(); i++) { if (topbottoms[i] == 0) { - point[i] = r.LowerBoundOfCoord(i); + point[i] = r.Side(i).LowerBound(); } else { - point[i] = r.UpperBoundOfCoord(i); + point[i] = r.Side(i).UpperBound(); } } if (this->RootNode()->GetData().Evaluate(point) >= (T)0) { @@ -359,7 +358,7 @@ bool TreeOfPartials::MultiaffinePolyEverywhereNegativeIn(const gRectangle return true; } -template bool TreeOfPartials::PolyEverywhereNegativeIn(const gRectangle &r) const +template bool TreeOfPartials::PolyEverywhereNegativeIn(const Rectangle &r) const { if (this->RootNode()->GetData().IsMultiaffine()) { return MultiaffinePolyEverywhereNegativeIn(r); diff --git a/src/solvers/enumpoly/ineqsolv.h b/src/solvers/enumpoly/ineqsolv.h index 328e2ac9b..bf8abba1e 100644 --- a/src/solvers/enumpoly/ineqsolv.h +++ b/src/solvers/enumpoly/ineqsolv.h @@ -26,7 +26,7 @@ #include "gambit.h" #include "odometer.h" -#include "rectangl.h" +#include "rectangle.h" #include "gpoly.h" #include "gpolylst.h" #include "gpartltr.h" @@ -59,9 +59,9 @@ template class IneqSolv { // Routines Doing the Actual Work bool IsASolution(const Gambit::Vector &) const; - bool SystemHasNoSolutionIn(const gRectangle &r, Gambit::Array &) const; + bool SystemHasNoSolutionIn(const Rectangle &r, Gambit::Array &) const; - bool ASolutionExistsRecursion(const gRectangle &, Gambit::Vector &, + bool ASolutionExistsRecursion(const Rectangle &, Gambit::Vector &, Gambit::Array &) const; public: @@ -81,7 +81,7 @@ template class IneqSolv { T ErrorTolerance() const { return Epsilon; } // The function that does everything - bool ASolutionExists(const gRectangle &, Gambit::Vector &sample); + bool ASolutionExists(const Rectangle &, Gambit::Vector &sample); }; #endif // INEQSOLV_H diff --git a/src/solvers/enumpoly/ineqsolv.imp b/src/solvers/enumpoly/ineqsolv.imp index 86acee360..4c269ee42 100644 --- a/src/solvers/enumpoly/ineqsolv.imp +++ b/src/solvers/enumpoly/ineqsolv.imp @@ -97,7 +97,7 @@ template bool IneqSolv::IsASolution(const Gambit::Vector &v) con } template -bool IneqSolv::SystemHasNoSolutionIn(const gRectangle &r, +bool IneqSolv::SystemHasNoSolutionIn(const Rectangle &r, Gambit::Array &precedence) const { for (int i = 1; i <= System.Length(); i++) { @@ -127,7 +127,7 @@ bool IneqSolv::SystemHasNoSolutionIn(const gRectangle &r, } template -bool IneqSolv::ASolutionExistsRecursion(const gRectangle &r, Gambit::Vector &sample, +bool IneqSolv::ASolutionExistsRecursion(const Rectangle &r, Gambit::Vector &sample, Gambit::Array &precedence) const { /* @@ -148,9 +148,8 @@ bool IneqSolv::ASolutionExistsRecursion(const gRectangle &r, Gambit::Vecto return false; } - int N = r.NumberOfCellsInSubdivision(); - for (int i = 1; i <= N; i++) { - if (ASolutionExistsRecursion(r.SubdivisionCell(i), sample, precedence)) { + for (const auto &cell : r.Orthants()) { + if (ASolutionExistsRecursion(cell, sample, precedence)) { return true; } } @@ -158,7 +157,7 @@ bool IneqSolv::ASolutionExistsRecursion(const gRectangle &r, Gambit::Vecto } template -bool IneqSolv::ASolutionExists(const gRectangle &r, Gambit::Vector &sample) +bool IneqSolv::ASolutionExists(const Rectangle &r, Gambit::Vector &sample) { // precedence orders search for everywhere negative poly Gambit::Array precedence(System.Length()); diff --git a/src/solvers/enumpoly/interval.h b/src/solvers/enumpoly/interval.h deleted file mode 100644 index be8ee3471..000000000 --- a/src/solvers/enumpoly/interval.h +++ /dev/null @@ -1,89 +0,0 @@ -// -// This file is part of Gambit -// Copyright (c) 1994-2024, The Gambit Project (http://www.gambit-project.org) -// -// FILE: src/tools/enumpoly/interval.h -// Interface to interval type -// -// 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 INTERVAL_H -#define INTERVAL_H - -#include "gambit.h" -#include "core/rational.h" - -/* This file provides the template class - - gInterval - -which models the concept of a nonempty compact interval. -Since boundary points can be identified, open and half -open (bounded) intervals can be effected, but less directly. -*/ - -template class gInterval { -private: - T lower_bd; - T upper_bd; - -public: - // constructors - gInterval(const gInterval &p_interval) - : lower_bd(p_interval.lower_bd), upper_bd(p_interval.upper_bd) - { - } - gInterval(const T &p_low, const T &p_high) : lower_bd(p_low), upper_bd(p_high) {} - ~gInterval() = default; - - gInterval &operator=(const gInterval &y) = delete; - - // operators - bool operator==(const gInterval &p_rhs) const - { - return lower_bd == p_rhs.lower_bd && upper_bd == p_rhs.upper_bd; - } - bool operator!=(const gInterval &p_rhs) const - { - return lower_bd != p_rhs.lower_bd || upper_bd != p_rhs.upper_bd; - } - - // information - const T &LowerBound() const { return lower_bd; } - const T &UpperBound() const { return upper_bd; } - bool Contains(const T &p_number) const { return lower_bd <= p_number && p_number <= upper_bd; } - bool Contains(const gInterval &p_interval) const - { - return (lower_bd <= p_interval.lower_bd && p_interval.upper_bd <= upper_bd); - } - bool LiesBelow(const T &p_number) const { return upper_bd <= p_number; } - bool LiesAbove(const T &p_number) const { return p_number <= lower_bd; } - T Length() const { return upper_bd - lower_bd; } - T Midpoint() const { return (upper_bd + lower_bd) / (T)2; } - gInterval LeftHalf() const { return gInterval(LowerBound(), Midpoint()); } - gInterval RightHalf() const { return gInterval(Midpoint(), UpperBound()); } - - gInterval SameCenterTwiceLength() const - { - return gInterval((T)2 * LowerBound() - Midpoint(), (T)2 * UpperBound() - Midpoint()); - } - gInterval SameCenterWithNewLength(const T &p_length) const - { - return gInterval(Midpoint() - p_length / (T)2, Midpoint() + p_length / (T)2); - } -}; - -#endif // INTERVAL_H diff --git a/src/solvers/enumpoly/nfgpoly.cc b/src/solvers/enumpoly/nfgpoly.cc index 3521f9dfd..58ae0f165 100644 --- a/src/solvers/enumpoly/nfgpoly.cc +++ b/src/solvers/enumpoly/nfgpoly.cc @@ -28,7 +28,7 @@ #include "solvers/nashsupport/nashsupport.h" #include "gpoly.h" #include "gpolylst.h" -#include "rectangl.h" +#include "rectangle.h" #include "quiksolv.h" using namespace Gambit; diff --git a/src/solvers/enumpoly/quiksolv.h b/src/solvers/enumpoly/quiksolv.h index a6ec4515a..da246b700 100644 --- a/src/solvers/enumpoly/quiksolv.h +++ b/src/solvers/enumpoly/quiksolv.h @@ -25,7 +25,7 @@ #include "gambit.h" #include "odometer.h" -#include "rectangl.h" +#include "rectangle.h" #include "gpoly.h" #include "gpolylst.h" #include "gpartltr.h" @@ -85,30 +85,30 @@ template class QuikSolv { // Check whether roots are impossible - bool SystemHasNoRootsIn(const gRectangle &r, Gambit::Array &) const; + bool SystemHasNoRootsIn(const Rectangle &r, Gambit::Array &) const; // Ask whether Newton's method leads to a root - bool NewtonRootInRectangle(const gRectangle &, Gambit::Vector &) const; + bool NewtonRootInRectangle(const Rectangle &, Gambit::Vector &) const; // Ask whether we can prove that there is no root other than // the one produced by the last step double - MaxDistanceFromPointToVertexAfterTransformation(const gRectangle &, + MaxDistanceFromPointToVertexAfterTransformation(const Rectangle &, const Gambit::Vector &, const Gambit::SquareMatrix &) const; - bool HasNoOtherRootsIn(const gRectangle &, const Gambit::Vector &, + bool HasNoOtherRootsIn(const Rectangle &, const Gambit::Vector &, const Gambit::SquareMatrix &) const; // Combine the last two steps into a single query - bool NewtonRootIsOnlyInRct(const gRectangle &, Gambit::Vector &) const; + bool NewtonRootIsOnlyInRct(const Rectangle &, Gambit::Vector &) const; // Recursive parts of recursive methods - void FindRootsRecursion(Gambit::List> *, const gRectangle &, + void FindRootsRecursion(Gambit::List> *, const Rectangle &, const int &, Gambit::Array &, int &iterations, int depth, const int &, int *) const; @@ -135,7 +135,7 @@ template class QuikSolv { Gambit::Vector SlowNewtonPolishOnce(const Gambit::Vector &) const; // The grand calculation - returns true if successful - bool FindCertainNumberOfRoots(const gRectangle &, const int &, const int &); + bool FindCertainNumberOfRoots(const Rectangle &, const int &, const int &); }; #endif // QUIKSOLV_H diff --git a/src/solvers/enumpoly/quiksolv.imp b/src/solvers/enumpoly/quiksolv.imp index b300312cf..48afd5969 100644 --- a/src/solvers/enumpoly/quiksolv.imp +++ b/src/solvers/enumpoly/quiksolv.imp @@ -77,7 +77,7 @@ template Gambit::RectArray QuikSolv::Eq_i_Uses_j() const //--------------------------- template -bool QuikSolv::SystemHasNoRootsIn(const gRectangle &r, +bool QuikSolv::SystemHasNoRootsIn(const Rectangle &r, Gambit::Array &precedence) const { for (int i = 1; i <= System.Length(); i++) { @@ -144,7 +144,7 @@ static bool fuzzy_equals(const Gambit::Vector &x, const Gambit::Vector -bool QuikSolv::NewtonRootInRectangle(const gRectangle &r, +bool QuikSolv::NewtonRootInRectangle(const Rectangle &r, Gambit::Vector &point) const { // assert (NoEquations == System.Dmnsn()); @@ -160,7 +160,7 @@ bool QuikSolv::NewtonRootInRectangle(const gRectangle &r, return r.Contains(point); } - gRectangle bigr = r.SameCenterDoubleSideLengths(); + Rectangle bigr = r.SameCenterDoubleLengths(); Gambit::Vector newpoint(Dmnsn()); @@ -173,11 +173,11 @@ bool QuikSolv::NewtonRootInRectangle(const gRectangle &r, int direction = 1; while (direction < Dmnsn() && !nonsingular) { Gambit::Vector perturbed_point(point); - if (r.UpperBoundOfCoord(direction) > point[direction]) { - perturbed_point[direction] += (r.UpperBoundOfCoord(direction) - point[direction]) / 10; + if (r.Side(direction).UpperBound() > point[direction]) { + perturbed_point[direction] += (r.Side(direction).UpperBound() - point[direction]) / 10; } else { - perturbed_point[direction] += (r.LowerBoundOfCoord(direction) - point[direction]) / 10; + perturbed_point[direction] += (r.Side(direction).LowerBound() - point[direction]) / 10; } nonsingular = true; @@ -191,11 +191,11 @@ bool QuikSolv::NewtonRootInRectangle(const gRectangle &r, } if (!nonsingular) { Gambit::Vector perturbed_point(point); - if (r.UpperBoundOfCoord(direction) > point[direction]) { - perturbed_point[direction] += (r.UpperBoundOfCoord(direction) - point[direction]) / 10; + if (r.Side(direction).UpperBound() > point[direction]) { + perturbed_point[direction] += (r.Side(direction).UpperBound() - point[direction]) / 10; } else { - perturbed_point[direction] += (r.LowerBoundOfCoord(direction) - point[direction]) / 10; + perturbed_point[direction] += (r.Side(direction).LowerBound() - point[direction]) / 10; } newpoint = point + (NewtonPolishOnce(perturbed_point) - perturbed_point); } @@ -232,7 +232,7 @@ bool QuikSolv::NewtonRootInRectangle(const gRectangle &r, template double QuikSolv::MaxDistanceFromPointToVertexAfterTransformation( - const gRectangle &r, const Gambit::Vector &p, + const Rectangle &r, const Gambit::Vector &p, const Gambit::SquareMatrix &M) const { // A very early implementation of this method used a type gDouble which @@ -255,10 +255,10 @@ double QuikSolv::MaxDistanceFromPointToVertexAfterTransformation( Gambit::Vector diffs(Dmnsn()); for (int i = 1; i <= Dmnsn(); i++) { if (ListOfTopBottoms[i] == 2) { - diffs[i] = r.CartesianFactor(i).UpperBound() - p[i]; + diffs[i] = r.Side(i).UpperBound() - p[i]; } else { - diffs[i] = p[i] - r.CartesianFactor(i).LowerBound(); + diffs[i] = p[i] - r.Side(i).LowerBound(); } } Gambit::Vector new_diffs = M * diffs; @@ -272,7 +272,7 @@ double QuikSolv::MaxDistanceFromPointToVertexAfterTransformation( } template -bool QuikSolv::HasNoOtherRootsIn(const gRectangle &r, const Gambit::Vector &p, +bool QuikSolv::HasNoOtherRootsIn(const Rectangle &r, const Gambit::Vector &p, const Gambit::SquareMatrix &M) const { // assert (NoEquations == System.Dmnsn()); @@ -297,7 +297,7 @@ bool QuikSolv::HasNoOtherRootsIn(const gRectangle &r, const Gambit::V //-------------------------------------------- template -bool QuikSolv::NewtonRootIsOnlyInRct(const gRectangle &r, +bool QuikSolv::NewtonRootIsOnlyInRct(const Rectangle &r, Gambit::Vector &point) const { // assert (NoEquations == System.Dmnsn()); @@ -392,7 +392,7 @@ Gambit::Vector QuikSolv::SlowNewtonPolishOnce(const Gambit::Vector -bool QuikSolv::FindCertainNumberOfRoots(const gRectangle &r, const int &max_iterations, +bool QuikSolv::FindCertainNumberOfRoots(const Rectangle &r, const int &max_iterations, const int &max_no_roots) { auto *rootlistptr = new Gambit::List>(); @@ -414,8 +414,8 @@ bool QuikSolv::FindCertainNumberOfRoots(const gRectangle &r, const int &ma int iterations = 0; int *no_found = new int(0); - FindRootsRecursion(rootlistptr, TogDouble(r), max_iterations, precedence, iterations, 1, - max_no_roots, no_found); + FindRootsRecursion(rootlistptr, r, max_iterations, precedence, iterations, 1, max_no_roots, + no_found); if (iterations < max_iterations) { Roots = *rootlistptr; @@ -428,7 +428,7 @@ bool QuikSolv::FindCertainNumberOfRoots(const gRectangle &r, const int &ma template void QuikSolv::FindRootsRecursion(Gambit::List> *rootlistptr, - const gRectangle &r, const int &max_iterations, + const Rectangle &r, const int &max_iterations, Gambit::Array &precedence, int &iterations, int depth, const int &max_no_roots, int *roots_found) const { @@ -470,17 +470,14 @@ void QuikSolv::FindRootsRecursion(Gambit::List> *rootl return; } - int N = r.NumberOfCellsInSubdivision(); - for (int i = 1; i <= N; i++) { + for (const auto &cell : r.Orthants()) { if (max_no_roots == 0 || *roots_found < max_no_roots) { if (iterations >= max_iterations || depth == MAX_DEPTH) { return; } - else { - iterations++; - FindRootsRecursion(rootlistptr, r.SubdivisionCell(i), max_iterations, precedence, - iterations, depth + 1, max_no_roots, roots_found); - } + iterations++; + FindRootsRecursion(rootlistptr, cell, max_iterations, precedence, iterations, depth + 1, + max_no_roots, roots_found); } } } diff --git a/src/solvers/enumpoly/rectangl.cc b/src/solvers/enumpoly/rectangl.cc deleted file mode 100644 index 7b094270c..000000000 --- a/src/solvers/enumpoly/rectangl.cc +++ /dev/null @@ -1,26 +0,0 @@ -// -// This file is part of Gambit -// Copyright (c) 1994-2024, The Gambit Project (http://www.gambit-project.org) -// -// FILE: src/tools/enumpoly/rectangl.cc -// Instantiation of rectangle classes -// -// This program is free software; you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation; either version 2 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -// - -#include "rectangl.imp" - -template class gRectangle; -template gRectangle TogDouble(const gRectangle &); diff --git a/src/solvers/enumpoly/rectangl.h b/src/solvers/enumpoly/rectangl.h deleted file mode 100644 index 3552e81f4..000000000 --- a/src/solvers/enumpoly/rectangl.h +++ /dev/null @@ -1,84 +0,0 @@ -// -// This file is part of Gambit -// Copyright (c) 1994-2024, The Gambit Project (http://www.gambit-project.org) -// -// FILE: src/tools/enumpoly/rectangl.h -// Declaration of rectangle 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 RECTANGL_H -#define RECTANGL_H - -#include "interval.h" -#include "core/vector.h" - -/* This file provides the template class - - gRectangle - -which models the concept of a nonempty compact interval. -Since boundary points can be identified, open and half -open (bounded) intervals can be effected, but less directly. -*/ - -template class gRectangle { -private: - Gambit::List> sides; - -public: - // constructors - gRectangle(const gRectangle &); - explicit gRectangle(const Gambit::List> &); - gRectangle(const Gambit::Vector &, const Gambit::Vector &); - ~gRectangle(); - - // operators - gRectangle &operator=(const gRectangle &y); - bool operator==(const gRectangle &y) const; - bool operator!=(const gRectangle &y) const; - - // information - const int Dmnsn() const; - Gambit::Vector LowerBound() const; - Gambit::Vector UpperBound() const; - const T LowerBoundOfCoord(const int &) const; - const T UpperBoundOfCoord(const int &) const; - const T HeightInCoord(const int &) const; - const gInterval CartesianFactor(const int &) const; - const gRectangle SameCenterDoubleSideLengths() const; - const gRectangle CubeContainingCrcmscrbngSphere() const; - const gRectangle Orthant(const Gambit::Array &) const; - const Gambit::Vector SideLengths() const; - const T MaximalSideLength() const; - bool Contains(const Gambit::Vector &, const T &eps = T(0)) const; - bool Contains(const gRectangle &) const; - const T Volume() const; - const Gambit::Vector Center() const; - const gRectangle BoundingRectangle() const; - const Gambit::List> VertexList() const; - const int NumberOfCellsInSubdivision() const; - const gRectangle SubdivisionCell(const int &) const; - const T DiameterSquared() const; -}; - -//------------- -// Conversion: -//------------- - -template gRectangle TogDouble(const gRectangle &); - -#endif // RECTANGL_H diff --git a/src/solvers/enumpoly/rectangl.imp b/src/solvers/enumpoly/rectangl.imp deleted file mode 100644 index 8e125f4b9..000000000 --- a/src/solvers/enumpoly/rectangl.imp +++ /dev/null @@ -1,307 +0,0 @@ -// -// This file is part of Gambit -// Copyright (c) 1994-2024, The Gambit Project (http://www.gambit-project.org) -// -// FILE: src/tools/enumpoly/rectangl.imp -// Implementation of rectangle 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 -#include "rectangl.h" -#include "odometer.h" - -//-------------------------------------------------------------------------- -// rectangle -- constructors and destructor -//-------------------------------------------------------------------------- - -template gRectangle::gRectangle(const gRectangle &given) : sides(given.sides) {} - -template -gRectangle::gRectangle(const Gambit::List> &given) : sides(given) -{ -} - -template -gRectangle::gRectangle(const Gambit::Vector &lower_bd, const Gambit::Vector &upper_bd) - : sides() -{ - // assert (lower_bd.Check(upper_bd)); - for (int i = 1; i <= upper_bd.size(); i++) { - gInterval side(lower_bd[i], upper_bd[i]); - sides.push_back(side); - } -} - -template gRectangle::~gRectangle() = default; - -//-------------------------------------------------------------------------- -// rectangle -- operators -//-------------------------------------------------------------------------- - -template gRectangle &gRectangle::operator=(const gRectangle & /* rhs */) -{ - // gout << "For const'ness, operator = not allowed for gRectangles\n"; - exit(0); - return *this; -} - -template bool gRectangle::operator==(const gRectangle &rhs) const -{ - for (int i = 1; i <= Dmnsn(); i++) { - if (sides[i] != rhs.sides[i]) { - return false; - } - } - return true; -} - -template bool gRectangle::operator!=(const gRectangle &rhs) const -{ - return !(*this == rhs); -} - -//-------------------------------------------------------------------------- -// interval -- information -//-------------------------------------------------------------------------- - -template const int gRectangle::Dmnsn() const { return sides.size(); } - -template Gambit::Vector gRectangle::LowerBound() const -{ - Gambit::Vector answer(Dmnsn()); - for (int i = 1; i <= Dmnsn(); i++) { - answer[i] = sides[i].LowerBound(); - } - return answer; -} - -template Gambit::Vector gRectangle::UpperBound() const -{ - Gambit::Vector answer(Dmnsn()); - for (int i = 1; i <= Dmnsn(); i++) { - answer[i] = sides[i].UpperBound(); - } - return answer; -} - -template const T gRectangle::LowerBoundOfCoord(const int &i) const -{ - // assert (1 <= i && i <= Dmnsn()); - return sides[i].LowerBound(); -} - -template const T gRectangle::UpperBoundOfCoord(const int &i) const -{ - // assert (1 <= i && i <= Dmnsn()); - return sides[i].UpperBound(); -} - -template const T gRectangle::HeightInCoord(const int &i) const -{ - // assert (1 <= i && i <= Dmnsn()); - return sides[i].Length(); -} - -template const gInterval gRectangle::CartesianFactor(const int &i) const -{ - return sides[i]; -} - -template const gRectangle gRectangle::SameCenterDoubleSideLengths() const -{ - Gambit::List> new_sides; - for (int i = 1; i <= Dmnsn(); i++) { - new_sides.push_back(CartesianFactor(i).SameCenterTwiceLength()); - } - return gRectangle(new_sides); -} - -template const gRectangle gRectangle::CubeContainingCrcmscrbngSphere() const -{ - T maxlength((T)0); - T sumsquares((T)0); - int i; - for (i = 1; i <= Dmnsn(); i++) { - sumsquares += sides[i].Length() * sides[i].Length(); - if (sides[i].Length() > maxlength) { - maxlength = sides[i].Length(); - } - } - T diameter((T)0); - while (diameter * diameter < sumsquares) { - diameter += maxlength; - } - - Gambit::List> new_sides; - for (i = 1; i <= Dmnsn(); i++) { - new_sides.push_back(CartesianFactor(i).SameCenterWithNewLength(diameter)); - } - return gRectangle(new_sides); -} - -template -const gRectangle gRectangle::Orthant(const Gambit::Array &top_or_bot) const -{ - Gambit::List> new_sides; - for (int i = 1; i <= Dmnsn(); i++) { - if (top_or_bot[i] == 0) { - new_sides.push_back(CartesianFactor(i).LeftHalf()); - } - else { - new_sides.push_back(CartesianFactor(i).RightHalf()); - } - } - return gRectangle(new_sides); -} - -template const Gambit::Vector gRectangle::SideLengths() const -{ - Gambit::Vector answer(Dmnsn()); - for (int i = 1; i <= Dmnsn(); i++) { - answer[i] = sides[i].UpperBound() - sides[i].LowerBound(); - } - return answer; -} - -template const T gRectangle::MaximalSideLength() const -{ - T answer((T)0); - for (int i = 1; i <= Dmnsn(); i++) { - if (HeightInCoord(i) > answer) { - answer = HeightInCoord(i); - } - } - return answer; -} - -template bool gRectangle::Contains(const Gambit::Vector &point, const T &eps) const -{ - // assert (point.Length() == Dmnsn()); - - for (int i = 1; i <= Dmnsn(); i++) { - if (point[i] + eps < sides[i].LowerBound() || sides[i].UpperBound() + eps < point[i]) { - return false; - } - } - return true; -} - -template bool gRectangle::Contains(const gRectangle &R) const -{ - // assert (R.Dmnsn() == Dmnsn()); - - for (int i = 1; i <= Dmnsn(); i++) { - if (!sides[i].Contains(R.sides[i])) { - return false; - } - } - return true; -} - -template const T gRectangle::Volume() const -{ - T answer = (T)1; - for (int i = 1; i <= Dmnsn(); i++) { - answer *= (sides[i].UpperBound() - sides[i].LowerBound()); - } - return answer; -} - -template const Gambit::Vector gRectangle::Center() const -{ - Gambit::Vector answer(Dmnsn()); - for (int i = 1; i <= Dmnsn(); i++) { - answer[i] = (sides[i].UpperBound() + sides[i].LowerBound()) / ((T)2); - } - return answer; -} - -template const gRectangle gRectangle::BoundingRectangle() const { return *this; } - -template const Gambit::List> gRectangle::VertexList() const -{ - Gambit::List> answer; - - Gambit::Array ListOfTwos(Dmnsn()); - for (int i = 1; i <= Dmnsn(); i++) { - ListOfTwos[i] = 2; - } - gIndexOdometer ListOfTopBottoms(ListOfTwos); - - while (ListOfTopBottoms.Turn()) { - Gambit::Vector next(Dmnsn()); - for (int i = 1; i <= Dmnsn(); i++) { - if (ListOfTopBottoms[i] == 1) { - next[i] = LowerBoundOfCoord(i); - } - else { - next[i] = UpperBoundOfCoord(i); - } - } - answer.push_back(next); - } - return answer; -} - -template const int gRectangle::NumberOfCellsInSubdivision() const -{ - int answer = 1; - for (int i = 1; i <= Dmnsn(); i++) { - answer *= 2; - } - return answer; -} - -template const gRectangle gRectangle::SubdivisionCell(const int &index) const -{ - int tmp = index; - Gambit::Array updowns(Dmnsn()); - for (int i = 1; i <= Dmnsn(); i++) { - if (tmp % 2 == 1) { - updowns[i] = 1; - tmp--; - } - else { - updowns[i] = 0; - } - tmp /= 2; - } - return Orthant(updowns); -} - -template const T gRectangle::DiameterSquared() const -{ - T answer((T)0); - for (int i = 1; i <= Dmnsn(); i++) { - answer += HeightInCoord(i) * HeightInCoord(i); - } - return answer; -} - -//---------------------------------- -// Conversion -//---------------------------------- - -template gRectangle TogDouble(const gRectangle &given) -{ - Gambit::List> cartesian_factors; - for (int i = 1; i <= given.Dmnsn(); i++) { - cartesian_factors.push_back( - gInterval((double)given.LowerBound()[i], (double)given.UpperBound()[i])); - } - return gRectangle(cartesian_factors); -} diff --git a/src/solvers/enumpoly/rectangle.h b/src/solvers/enumpoly/rectangle.h new file mode 100644 index 000000000..532161052 --- /dev/null +++ b/src/solvers/enumpoly/rectangle.h @@ -0,0 +1,180 @@ +// +// This file is part of Gambit +// Copyright (c) 1994-2024, The Gambit Project (http://www.gambit-project.org) +// +// FILE: src/tools/enumpoly/rectangle.h +// Declaration of rectangle 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 RECTANGLE_H +#define RECTANGLE_H + +#include "gambit.h" + +using namespace Gambit; + +/// A nonempty compact interval +template class Interval { +private: + T lower_bd, upper_bd; + +public: + Interval(const Interval &p_interval) + : lower_bd(p_interval.lower_bd), upper_bd(p_interval.upper_bd) + { + } + Interval(const T &p_low, const T &p_high) : lower_bd(p_low), upper_bd(p_high) {} + ~Interval() = default; + + Interval &operator=(const Interval &y) = delete; + + bool operator==(const Interval &p_rhs) const + { + return lower_bd == p_rhs.lower_bd && upper_bd == p_rhs.upper_bd; + } + bool operator!=(const Interval &p_rhs) const + { + return lower_bd != p_rhs.lower_bd || upper_bd != p_rhs.upper_bd; + } + + const T &LowerBound() const { return lower_bd; } + const T &UpperBound() const { return upper_bd; } + bool Contains(const T &p_number) const { return lower_bd <= p_number && p_number <= upper_bd; } + bool Contains(const Interval &p_interval) const + { + return lower_bd <= p_interval.lower_bd && p_interval.upper_bd <= upper_bd; + } + T Length() const { return upper_bd - lower_bd; } + T Midpoint() const { return (upper_bd + lower_bd) / static_cast(2); } + Interval LeftHalf() const { return {LowerBound(), Midpoint()}; } + Interval RightHalf() const { return {Midpoint(), UpperBound()}; } + + Interval SameCenterDoubleLength() const + { + return {static_cast(2) * LowerBound() - Midpoint(), + static_cast(2) * UpperBound() - Midpoint()}; + } +}; + +/// A cartesian product of intervals +template class Rectangle { +private: + List> sides; + + Rectangle() = default; + +public: + /// Represents the subdivision of a rectangle into its constitutent orthants + class Orthants { + private: + Rectangle m_rect; + + public: + class iterator { + friend class Orthants; + + private: + const Orthants *m_orthants; + int m_index; + + iterator(const Orthants *orth, int index) : m_orthants(orth), m_index(index) {} + + public: + Rectangle operator*() + { + Rectangle ret; + int tmp = m_index; + for (const auto &side : m_orthants->m_rect.sides) { + if (tmp % 2 == 1) { + ret.sides.push_back(side.RightHalf()); + tmp--; + } + else { + ret.sides.push_back(side.LeftHalf()); + } + tmp /= 2; + } + return ret; + } + + iterator &operator++() + { + m_index++; + return *this; + } + + bool operator==(const iterator &it) const + { + return (m_orthants == it.m_orthants) && (m_index == it.m_index); + } + bool operator!=(const iterator &it) const { return !(*this == it); } + }; + + Orthants(const Rectangle &p_rect) : m_rect(p_rect) {} + + int size() const { return std::pow(2, m_rect.Dmnsn()); } + iterator begin() const { return {this, 1}; } + iterator end() const { return {this, size() + 1}; } + }; + + Rectangle(const Vector &lower, const Vector &upper) + { + std::transform(lower.begin(), lower.end(), upper.begin(), std::back_inserter(sides), + [](const T &x, const T &y) { return Interval(x, y); }); + } + Rectangle(const Rectangle &) = default; + ~Rectangle() = default; + + Rectangle &operator=(const Rectangle &) = delete; + bool operator==(const Rectangle &y) const { return sides == y.sides; } + bool operator!=(const Rectangle &y) const { return sides != y.sides; } + + int Dmnsn() const { return sides.size(); } + Orthants Orthants() const { return {*this}; } + const Rectangle SameCenterDoubleLengths() const + { + Rectangle ret; + for (const auto &side : sides) { + ret.sides.push_back(side.SameCenterDoubleLength()); + } + return ret; + } + const Interval &Side(const int dim) const { return sides[dim]; } + Vector SideLengths() const + { + Vector answer(Dmnsn()); + std::transform(sides.begin(), sides.end(), answer.begin(), + [](const Interval &x) { return x.Length(); }); + return answer; + } + bool Contains(const Vector &point, const T &eps = static_cast(0)) const + { + return std::equal(sides.begin(), sides.end(), point.begin(), + [&](const Interval &side, const T &v) { + return v + eps >= side.LowerBound() && side.UpperBound() + eps >= v; + }); + } + Vector Center() const + { + Vector answer(Dmnsn()); + std::transform(sides.begin(), sides.end(), answer.begin(), + [](const Interval &x) { return x.Midpoint(); }); + return answer; + } +}; + +#endif // RECTANGLE_H diff --git a/src/solvers/linalg/lhtab.imp b/src/solvers/linalg/lhtab.imp index 9572eafc2..5dbdc1d60 100644 --- a/src/solvers/linalg/lhtab.imp +++ b/src/solvers/linalg/lhtab.imp @@ -33,9 +33,8 @@ namespace linalg { template LHTableau::LHTableau(const Matrix &A1, const Matrix &A2, const Vector &b1, const Vector &b2) - : T1(A1, b1), T2(A2, b2), tmp1(b1.first_index(), b1.last_index()), tmp2(b2.first_index(), - b2.last_index()), - solution(b1.first_index(), b2.last_index()) + : T1(A1, b1), T2(A2, b2), tmp1(b1.first_index(), b1.last_index()), + tmp2(b2.first_index(), b2.last_index()), solution(b1.first_index(), b2.last_index()) { } diff --git a/src/solvers/linalg/lpsolve.imp b/src/solvers/linalg/lpsolve.imp index aed8ee785..e6b834af0 100644 --- a/src/solvers/linalg/lpsolve.imp +++ b/src/solvers/linalg/lpsolve.imp @@ -30,8 +30,7 @@ template LPSolve::LPSolve(const Matrix &A, const Vector &b, const Vector &c, int nequals) : well_formed(true), feasible(true), bounded(true), nvars(c.size()), neqns(b.size()), nequals(nequals), total_cost(0), tmin(0), tab(A, Artificials(b), b), UB(nullptr), LB(nullptr), - ub(nullptr), lb(nullptr), xx(nullptr), cost(nullptr), y(b.size()), x(b.size()), - d(b.size()) + ub(nullptr), lb(nullptr), xx(nullptr), cost(nullptr), y(b.size()), x(b.size()), d(b.size()) { // These are the values recommended by Murtagh (1981) for 15 digit // accuracy in LP problems From 8fbf142db6a3197a405f9d121578567ee72cc2fd Mon Sep 17 00:00:00 2001 From: Ted Turocy Date: Thu, 2 Jan 2025 11:02:24 +0000 Subject: [PATCH 49/99] Tidying and organisation of enumpoly This accomplishes a number of housekeeping tasks: * Removes unused code * Simplifies and modernises code in places * Changes/standardises naming of classes, variables, and so on to be more intuitive and following conventions --- Makefile.am | 36 +- src/core/vector.h | 6 + src/solvers/enumpoly/behavextend.cc | 153 +++--- src/solvers/enumpoly/behavextend.h | 2 +- src/solvers/enumpoly/efgpoly.cc | 53 +- src/solvers/enumpoly/enumpoly.h | 1 - src/solvers/enumpoly/gameseq.cc | 2 +- src/solvers/enumpoly/gameseq.h | 13 +- src/solvers/enumpoly/gcomplex.cc | 79 --- src/solvers/enumpoly/gcomplex.h | 85 ---- src/solvers/enumpoly/gpartltr.cc | 50 -- src/solvers/enumpoly/gpartltr.h | 118 ----- src/solvers/enumpoly/gpartltr.imp | 502 ------------------- src/solvers/enumpoly/gpoly.h | 376 -------------- src/solvers/enumpoly/gpolylst.cc | 37 -- src/solvers/enumpoly/gpolylst.h | 72 --- src/solvers/enumpoly/gpolylst.imp | 182 ------- src/solvers/enumpoly/gtree.h | 90 ---- src/solvers/enumpoly/gtree.imp | 184 ------- src/solvers/enumpoly/indexproduct.h | 89 ++++ src/solvers/enumpoly/ineqsolv.cc | 26 - src/solvers/enumpoly/ineqsolv.h | 87 ---- src/solvers/enumpoly/ineqsolv.imp | 176 ------- src/solvers/enumpoly/linrcomb.cc | 25 - src/solvers/enumpoly/linrcomb.h | 68 --- src/solvers/enumpoly/linrcomb.imp | 200 -------- src/solvers/enumpoly/ndarray.h | 4 +- src/solvers/enumpoly/nfgpoly.cc | 56 +-- src/solvers/enumpoly/odometer.cc | 82 --- src/solvers/enumpoly/odometer.h | 75 --- src/solvers/enumpoly/{gpoly.cc => poly.cc} | 16 +- src/solvers/enumpoly/poly.h | 339 +++++++++++++ src/solvers/enumpoly/{gpoly.imp => poly.imp} | 119 ++--- src/solvers/enumpoly/polyfeasible.h | 107 ++++ src/solvers/enumpoly/polypartial.h | 97 ++++ src/solvers/enumpoly/polypartial.imp | 199 ++++++++ src/solvers/enumpoly/polysolver.cc | 312 ++++++++++++ src/solvers/enumpoly/polysolver.h | 115 +++++ src/solvers/enumpoly/polysystem.h | 113 +++++ src/solvers/enumpoly/quiksolv.cc | 25 - src/solvers/enumpoly/quiksolv.h | 141 ------ src/solvers/enumpoly/quiksolv.imp | 483 ------------------ src/solvers/enumpoly/rectangle.h | 8 +- 43 files changed, 1601 insertions(+), 3402 deletions(-) delete mode 100644 src/solvers/enumpoly/gcomplex.cc delete mode 100644 src/solvers/enumpoly/gcomplex.h delete mode 100644 src/solvers/enumpoly/gpartltr.cc delete mode 100644 src/solvers/enumpoly/gpartltr.h delete mode 100644 src/solvers/enumpoly/gpartltr.imp delete mode 100644 src/solvers/enumpoly/gpoly.h delete mode 100644 src/solvers/enumpoly/gpolylst.cc delete mode 100644 src/solvers/enumpoly/gpolylst.h delete mode 100644 src/solvers/enumpoly/gpolylst.imp delete mode 100644 src/solvers/enumpoly/gtree.h delete mode 100644 src/solvers/enumpoly/gtree.imp create mode 100644 src/solvers/enumpoly/indexproduct.h delete mode 100644 src/solvers/enumpoly/ineqsolv.cc delete mode 100644 src/solvers/enumpoly/ineqsolv.h delete mode 100644 src/solvers/enumpoly/ineqsolv.imp delete mode 100644 src/solvers/enumpoly/linrcomb.cc delete mode 100644 src/solvers/enumpoly/linrcomb.h delete mode 100644 src/solvers/enumpoly/linrcomb.imp delete mode 100644 src/solvers/enumpoly/odometer.cc delete mode 100644 src/solvers/enumpoly/odometer.h rename src/solvers/enumpoly/{gpoly.cc => poly.cc} (73%) create mode 100644 src/solvers/enumpoly/poly.h rename src/solvers/enumpoly/{gpoly.imp => poly.imp} (57%) create mode 100644 src/solvers/enumpoly/polyfeasible.h create mode 100644 src/solvers/enumpoly/polypartial.h create mode 100644 src/solvers/enumpoly/polypartial.imp create mode 100644 src/solvers/enumpoly/polysolver.cc create mode 100644 src/solvers/enumpoly/polysolver.h create mode 100644 src/solvers/enumpoly/polysystem.h delete mode 100644 src/solvers/enumpoly/quiksolv.cc delete mode 100644 src/solvers/enumpoly/quiksolv.h delete mode 100644 src/solvers/enumpoly/quiksolv.imp diff --git a/Makefile.am b/Makefile.am index aef9f187b..31cdb5556 100644 --- a/Makefile.am +++ b/Makefile.am @@ -400,36 +400,22 @@ gambit_nashsupport_SOURCES = \ gambit_enumpoly_SOURCES = \ ${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/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/ineqsolv.cc \ - src/solvers/enumpoly/ineqsolv.h \ - src/solvers/enumpoly/ineqsolv.imp \ - src/solvers/enumpoly/quiksolv.cc \ - src/solvers/enumpoly/quiksolv.h \ - src/solvers/enumpoly/quiksolv.imp \ + 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/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/ndarray.h \ - src/solvers/enumpoly/odometer.cc \ - src/solvers/enumpoly/odometer.h \ src/solvers/enumpoly/efgpoly.cc \ src/solvers/enumpoly/nfgpoly.cc \ src/solvers/enumpoly/enumpoly.h \ diff --git a/src/core/vector.h b/src/core/vector.h index 21e2344c8..dd6bfdebe 100644 --- a/src/core/vector.h +++ b/src/core/vector.h @@ -130,6 +130,12 @@ template class Vector : public Array { 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)) { diff --git a/src/solvers/enumpoly/behavextend.cc b/src/solvers/enumpoly/behavextend.cc index 6e30d7d90..b348f8206 100644 --- a/src/solvers/enumpoly/behavextend.cc +++ b/src/solvers/enumpoly/behavextend.cc @@ -2,7 +2,7 @@ // This file is part of Gambit // Copyright (c) 1994-2024, The Gambit Project (http://www.gambit-project.org) // -// FILE: src/tools/enumpoly/behavextend.cc +// FILE: src/solver/enumpoly/behavextend.cc // Algorithms for extending behavior profiles to Nash equilibria // // This program is free software; you can redistribute it and/or modify @@ -21,15 +21,13 @@ // #include "behavextend.h" -#include "gpoly.h" -#include "gpolylst.h" -#include "rectangle.h" -#include "ineqsolv.h" - -using namespace Gambit; +#include "polysystem.h" +#include "polyfeasible.h" namespace { +using namespace Gambit; + void TerminalDescendants(const GameNode &p_node, std::list ¤t) { if (p_node->IsTerminal()) { @@ -50,14 +48,15 @@ std::list TerminalNodes(const Game &p_efg) } void DeviationInfosets(List &answer, const BehaviorSupportProfile &big_supp, - const GamePlayer &pl, const GameNode &node, const GameAction &act) + const GamePlayer &p_player, const GameNode &p_node, + const GameAction &p_action) { - GameNode child = node->GetChild(act); + GameNode child = p_node->GetChild(p_action); if (child->IsTerminal()) { return; } GameInfoset iset = child->GetInfoset(); - if (iset->GetPlayer() == pl) { + if (iset->GetPlayer() == p_player) { size_t insert = 0; bool done = false; while (!done) { @@ -70,36 +69,37 @@ void DeviationInfosets(List &answer, const BehaviorSupportProfile & } for (auto action : iset->GetActions()) { - DeviationInfosets(answer, big_supp, pl, child, action); + DeviationInfosets(answer, big_supp, p_player, child, action); } } -List DeviationInfosets(const BehaviorSupportProfile &big_supp, const GamePlayer &pl, - const GameInfoset &iset, const GameAction &act) +List DeviationInfosets(const BehaviorSupportProfile &big_supp, + const GamePlayer &p_player, const GameInfoset &p_infoset, + const GameAction &p_action) { List answer; - for (auto member : iset->GetMembers()) { - DeviationInfosets(answer, big_supp, pl, member, act); + for (auto member : p_infoset->GetMembers()) { + DeviationInfosets(answer, big_supp, p_player, member, p_action); } return answer; } -gPolyList ActionProbsSumToOneIneqs(const MixedBehaviorProfile &p_solution, - const VariableSpace &BehavStratSpace, - const BehaviorSupportProfile &big_supp, - const std::map &var_index) +PolynomialSystem ActionProbsSumToOneIneqs(const MixedBehaviorProfile &p_solution, + std::shared_ptr BehavStratSpace, + const BehaviorSupportProfile &big_supp, + const std::map &var_index) { - gPolyList answer(&BehavStratSpace); + PolynomialSystem answer(BehavStratSpace); for (auto player : p_solution.GetGame()->GetPlayers()) { for (auto infoset : player->GetInfosets()) { if (!big_supp.HasAction(infoset)) { int index_base = var_index.at(infoset); - gPoly factor(&BehavStratSpace, 1.0); + Polynomial factor(BehavStratSpace, 1.0); for (int k = 1; k < infoset->NumActions(); k++) { - factor -= gPoly(&BehavStratSpace, index_base + k, 1); + factor -= Polynomial(BehavStratSpace, index_base + k, 1); } - answer += factor; + answer.push_back(factor); } } } @@ -177,7 +177,8 @@ std::list DeviationSupports(const BehaviorSupportProfile } bool NashNodeProbabilityPoly(const MixedBehaviorProfile &p_solution, - gPoly &node_prob, const VariableSpace &BehavStratSpace, + Polynomial &node_prob, + std::shared_ptr BehavStratSpace, const BehaviorSupportProfile &dsupp, const std::map &var_index, GameNode tempnode, const GameInfoset &iset, const GameAction &act) @@ -209,12 +210,12 @@ bool NashNodeProbabilityPoly(const MixedBehaviorProfile &p_solution, int initial_var_no = var_index.at(last_infoset); if (last_action->GetNumber() < last_infoset->NumActions()) { int varno = initial_var_no + last_action->GetNumber(); - node_prob *= gPoly(&BehavStratSpace, varno, 1); + node_prob *= Polynomial(BehavStratSpace, varno, 1); } else { - gPoly factor(&BehavStratSpace, 1.0); + Polynomial factor(BehavStratSpace, 1.0); for (int k = 1; k < last_infoset->NumActions(); k++) { - factor -= gPoly(&BehavStratSpace, initial_var_no + k, 1); + factor -= Polynomial(BehavStratSpace, initial_var_no + k, 1); } node_prob *= factor; } @@ -224,13 +225,12 @@ bool NashNodeProbabilityPoly(const MixedBehaviorProfile &p_solution, return true; } -gPolyList NashExpectedPayoffDiffPolys(const MixedBehaviorProfile &p_solution, - const VariableSpace &BehavStratSpace, - const BehaviorSupportProfile &little_supp, - const BehaviorSupportProfile &big_supp, - const std::map &var_index) +PolynomialSystem NashExpectedPayoffDiffPolys( + const MixedBehaviorProfile &p_solution, std::shared_ptr BehavStratSpace, + const BehaviorSupportProfile &little_supp, const BehaviorSupportProfile &big_supp, + const std::map &var_index) { - gPolyList answer(&BehavStratSpace); + PolynomialSystem answer(BehavStratSpace); auto terminal_nodes = TerminalNodes(p_solution.GetGame()); @@ -249,10 +249,10 @@ gPolyList NashExpectedPayoffDiffPolys(const MixedBehaviorProfile // The utility difference between the // payoff resulting from the profile and deviation to // the strategy for pl specified by dsupp[k] - gPoly next_poly(&BehavStratSpace); + Polynomial next_poly(BehavStratSpace); for (auto node : terminal_nodes) { - gPoly node_prob(&BehavStratSpace, 1.0); + Polynomial node_prob(BehavStratSpace, 1.0); if (NashNodeProbabilityPoly(p_solution, node_prob, BehavStratSpace, support, var_index, node, infoset, action)) { if (node->GetOutcome()) { @@ -261,7 +261,7 @@ gPolyList NashExpectedPayoffDiffPolys(const MixedBehaviorProfile next_poly += node_prob; } } - answer += -next_poly + p_solution.GetPayoff(player); + answer.push_back(-next_poly + p_solution.GetPayoff(player)); } } } @@ -269,16 +269,16 @@ gPolyList NashExpectedPayoffDiffPolys(const MixedBehaviorProfile return answer; } -gPolyList ExtendsToNashIneqs(const MixedBehaviorProfile &p_solution, - const VariableSpace &BehavStratSpace, - const BehaviorSupportProfile &little_supp, - const BehaviorSupportProfile &big_supp, - const std::map &var_index) +PolynomialSystem ExtendsToNashIneqs(const MixedBehaviorProfile &p_solution, + std::shared_ptr BehavStratSpace, + const BehaviorSupportProfile &little_supp, + const BehaviorSupportProfile &big_supp, + const std::map &var_index) { - gPolyList answer(&BehavStratSpace); - answer += ActionProbsSumToOneIneqs(p_solution, BehavStratSpace, big_supp, var_index); - answer += - NashExpectedPayoffDiffPolys(p_solution, BehavStratSpace, little_supp, big_supp, var_index); + PolynomialSystem answer(BehavStratSpace); + answer.push_back(ActionProbsSumToOneIneqs(p_solution, BehavStratSpace, big_supp, var_index)); + answer.push_back( + NashExpectedPayoffDiffPolys(p_solution, BehavStratSpace, little_supp, big_supp, var_index)); return answer; } @@ -306,18 +306,15 @@ bool ExtendsToNash(const MixedBehaviorProfile &p_solution, } // We establish the space - VariableSpace BehavStratSpace(num_vars); + auto BehavStratSpace = std::make_shared(num_vars); - gPolyList inequalities = + PolynomialSystem inequalities = ExtendsToNashIneqs(p_solution, BehavStratSpace, little_supp, big_supp, var_index); // set up the rectangle of search Vector bottoms(num_vars), tops(num_vars); bottoms = 0; tops = 1; - - // Set up the test and do it - Vector sample(num_vars); - return IneqSolv(inequalities).ASolutionExists(Rectangle(bottoms, tops), sample); + return PolynomialFeasibilitySolver(inequalities).HasSolution(Rectangle(bottoms, tops)); } } // namespace Nash @@ -326,7 +323,8 @@ bool ExtendsToNash(const MixedBehaviorProfile &p_solution, namespace { bool ANFNodeProbabilityPoly(const MixedBehaviorProfile &p_solution, - gPoly &node_prob, const VariableSpace &BehavStratSpace, + Polynomial &node_prob, + std::shared_ptr BehavStratSpace, const BehaviorSupportProfile &big_supp, const std::map &var_index, GameNode tempnode, int pl, int i, int j) @@ -355,12 +353,12 @@ bool ANFNodeProbabilityPoly(const MixedBehaviorProfile &p_solution, int initial_var_no = var_index.at(last_infoset); if (last_action->GetNumber() < last_infoset->NumActions()) { int varno = initial_var_no + last_action->GetNumber(); - node_prob *= gPoly(&BehavStratSpace, varno, 1); + node_prob *= Polynomial(BehavStratSpace, varno, 1); } else { - gPoly factor(&BehavStratSpace, 1.0); + Polynomial factor(BehavStratSpace, 1.0); for (int k = 1; k < last_infoset->NumActions(); k++) { - factor -= gPoly(&BehavStratSpace, initial_var_no + k, 1); + factor -= Polynomial(BehavStratSpace, initial_var_no + k, 1); } node_prob *= factor; } @@ -370,13 +368,13 @@ bool ANFNodeProbabilityPoly(const MixedBehaviorProfile &p_solution, return true; } -gPolyList ANFExpectedPayoffDiffPolys(const MixedBehaviorProfile &p_solution, - const VariableSpace &BehavStratSpace, - const BehaviorSupportProfile &little_supp, - const BehaviorSupportProfile &big_supp, - const std::map &var_index) +PolynomialSystem ANFExpectedPayoffDiffPolys(const MixedBehaviorProfile &p_solution, + std::shared_ptr BehavStratSpace, + const BehaviorSupportProfile &little_supp, + const BehaviorSupportProfile &big_supp, + const std::map &var_index) { - gPolyList answer(&BehavStratSpace); + PolynomialSystem answer(BehavStratSpace); auto terminal_nodes = TerminalNodes(p_solution.GetGame()); @@ -392,9 +390,9 @@ gPolyList ANFExpectedPayoffDiffPolys(const MixedBehaviorProfile // This will be the utility difference between the // payoff resulting from the profile and deviation to // action j - gPoly next_poly(&BehavStratSpace); + Polynomial next_poly(BehavStratSpace); for (auto terminal : terminal_nodes) { - gPoly node_prob(&BehavStratSpace, 1.0); + Polynomial node_prob(BehavStratSpace, 1.0); if (ANFNodeProbabilityPoly(p_solution, node_prob, BehavStratSpace, big_supp, var_index, terminal, player->GetNumber(), infoset->GetNumber(), action->GetNumber())) { @@ -404,23 +402,23 @@ gPolyList ANFExpectedPayoffDiffPolys(const MixedBehaviorProfile next_poly += node_prob; } } - answer += -next_poly + p_solution.GetPayoff(player); + answer.push_back(-next_poly + p_solution.GetPayoff(player)); } } } return answer; } -gPolyList ExtendsToANFNashIneqs(const MixedBehaviorProfile &p_solution, - const VariableSpace &BehavStratSpace, - const BehaviorSupportProfile &little_supp, - const BehaviorSupportProfile &big_supp, - const std::map &var_index) +PolynomialSystem ExtendsToANFNashIneqs(const MixedBehaviorProfile &p_solution, + std::shared_ptr BehavStratSpace, + const BehaviorSupportProfile &little_supp, + const BehaviorSupportProfile &big_supp, + const std::map &var_index) { - gPolyList answer(&BehavStratSpace); - answer += ActionProbsSumToOneIneqs(p_solution, BehavStratSpace, big_supp, var_index); - answer += - ANFExpectedPayoffDiffPolys(p_solution, BehavStratSpace, little_supp, big_supp, var_index); + PolynomialSystem answer(BehavStratSpace); + answer.push_back(ActionProbsSumToOneIneqs(p_solution, BehavStratSpace, big_supp, var_index)); + answer.push_back( + ANFExpectedPayoffDiffPolys(p_solution, BehavStratSpace, little_supp, big_supp, var_index)); return answer; } @@ -446,9 +444,8 @@ bool ExtendsToAgentNash(const MixedBehaviorProfile &p_solution, } // We establish the space - VariableSpace BehavStratSpace(num_vars); - num_vars = BehavStratSpace.Dmnsn(); - gPolyList inequalities = + auto BehavStratSpace = std::make_shared(num_vars); + PolynomialSystem inequalities = ExtendsToANFNashIneqs(p_solution, BehavStratSpace, little_supp, big_supp, var_index); // set up the rectangle of search @@ -456,9 +453,7 @@ bool ExtendsToAgentNash(const MixedBehaviorProfile &p_solution, bottoms = 0; tops = 1; - // Set up the test and do it - Vector sample(num_vars); - return IneqSolv(inequalities).ASolutionExists(Rectangle(bottoms, tops), sample); + return PolynomialFeasibilitySolver(inequalities).HasSolution(Rectangle(bottoms, tops)); } } // namespace Nash diff --git a/src/solvers/enumpoly/behavextend.h b/src/solvers/enumpoly/behavextend.h index be8f5276e..971dd093b 100644 --- a/src/solvers/enumpoly/behavextend.h +++ b/src/solvers/enumpoly/behavextend.h @@ -2,7 +2,7 @@ // This file is part of Gambit // Copyright (c) 1994-2024, The Gambit Project (http://www.gambit-project.org) // -// FILE: src/tools/enumpoly/behavextend.h +// FILE: src/solvers/enumpoly/behavextend.h // Algorithms for extending behavior profiles to Nash equilibria // // This program is free software; you can redistribute it and/or modify diff --git a/src/solvers/enumpoly/efgpoly.cc b/src/solvers/enumpoly/efgpoly.cc index ed603fd6b..de0ca9f76 100644 --- a/src/solvers/enumpoly/efgpoly.cc +++ b/src/solvers/enumpoly/efgpoly.cc @@ -2,7 +2,7 @@ // This file is part of Gambit // Copyright (c) 1994-2024, The Gambit Project (http://www.gambit-project.org) // -// FILE: src/tools/enumpoly/efgpoly.cc +// FILE: src/solvers/enumpoly/efgpoly.cc // Enumerates all Nash equilibria of a game, via polynomial equations // // This program is free software; you can redistribute it and/or modify @@ -20,15 +20,11 @@ // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // -#include - #include "enumpoly.h" #include "solvers/nashsupport/nashsupport.h" #include "gameseq.h" -#include "gpoly.h" -#include "gpolylst.h" -#include "rectangle.h" -#include "quiksolv.h" +#include "polysystem.h" +#include "polysolver.h" #include "behavextend.h" using namespace Gambit; @@ -53,24 +49,24 @@ namespace { class ProblemData { public: GameSequenceForm sfg; - VariableSpace Space; + std::shared_ptr space; std::map var; - std::map> variables; + std::map> variables; explicit ProblemData(const BehaviorSupportProfile &p_support); }; -gPoly BuildSequenceVariable(ProblemData &p_data, const GameSequence &p_sequence, - const std::map &var) +Polynomial BuildSequenceVariable(ProblemData &p_data, const GameSequence &p_sequence, + const std::map &var) { if (!p_sequence->action) { - return {&p_data.Space, 1}; + return {p_data.space, 1}; } if (p_sequence->action != p_data.sfg.GetSupport().GetActions(p_sequence->GetInfoset()).back()) { - return {&p_data.Space, var.at(p_sequence), 1}; + return {p_data.space, var.at(p_sequence), 1}; } - gPoly equation(&p_data.Space); + Polynomial equation(p_data.space); for (auto seq : p_data.sfg.GetSequences(p_sequence->player)) { if (seq == p_sequence) { continue; @@ -85,7 +81,8 @@ gPoly BuildSequenceVariable(ProblemData &p_data, const GameSequence &p_s ProblemData::ProblemData(const BehaviorSupportProfile &p_support) : sfg(p_support), - Space(sfg.GetSequences().size() - sfg.GetInfosets().size() - sfg.GetPlayers().size()) + space(std::make_shared(sfg.GetSequences().size() - sfg.GetInfosets().size() - + sfg.GetPlayers().size())) { for (auto sequence : sfg.GetSequences()) { if (sequence->action && @@ -99,14 +96,14 @@ ProblemData::ProblemData(const BehaviorSupportProfile &p_support) } } -gPoly GetPayoff(ProblemData &p_data, const GamePlayer &p_player) +Polynomial GetPayoff(ProblemData &p_data, const GamePlayer &p_player) { - gPoly equation(&p_data.Space); + Polynomial equation(p_data.space); for (auto profile : p_data.sfg.GetContingencies()) { auto pay = p_data.sfg.GetPayoff(profile, p_player); if (pay != Rational(0)) { - gPoly term(&p_data.Space, double(pay)); + Polynomial term(p_data.space, double(pay)); for (auto player : p_data.sfg.GetPlayers()) { term *= p_data.variables.at(profile[player]); } @@ -116,15 +113,15 @@ gPoly GetPayoff(ProblemData &p_data, const GamePlayer &p_player) return equation; } -void IndifferenceEquations(ProblemData &p_data, gPolyList &p_equations) +void IndifferenceEquations(ProblemData &p_data, PolynomialSystem &p_equations) { for (auto player : p_data.sfg.GetPlayers()) { - gPoly payoff = GetPayoff(p_data, player); + Polynomial payoff = GetPayoff(p_data, player); for (auto sequence : p_data.sfg.GetSequences(player)) { try { - p_equations += payoff.PartialDerivative(p_data.var.at(sequence)); + p_equations.push_back(payoff.PartialDerivative(p_data.var.at(sequence))); } - catch (std::out_of_range) { + catch (std::out_of_range &) { // This sequence's variable was already substituted out in terms of // the probabilities of other sequences } @@ -132,7 +129,7 @@ void IndifferenceEquations(ProblemData &p_data, gPolyList &p_equations) } } -void LastActionProbPositiveInequalities(ProblemData &p_data, gPolyList &p_equations) +void LastActionProbPositiveInequalities(ProblemData &p_data, PolynomialSystem &p_equations) { for (auto sequence : p_data.sfg.GetSequences()) { if (!sequence->action) { @@ -140,7 +137,7 @@ void LastActionProbPositiveInequalities(ProblemData &p_data, gPolyList & } const auto &actions = p_data.sfg.GetSupport().GetActions(sequence->action->GetInfoset()); if (actions.size() > 1 && sequence->action == actions.back()) { - p_equations += p_data.variables.at(sequence); + p_equations.push_back(p_data.variables.at(sequence)); } } } @@ -158,18 +155,18 @@ std::list> SolveSupport(const BehaviorSupportProfil bool &p_isSingular, int p_stopAfter) { ProblemData data(p_support); - gPolyList equations(&data.Space); + PolynomialSystem equations(data.space); IndifferenceEquations(data, equations); LastActionProbPositiveInequalities(data, equations); // set up the rectangle of search - Vector bottoms(data.Space.Dmnsn()), tops(data.Space.Dmnsn()); + Vector bottoms(data.space->GetDimension()), tops(data.space->GetDimension()); bottoms = 0; tops = 1; - QuikSolv solver(equations); + PolynomialSystemSolver solver(equations); try { - solver.FindCertainNumberOfRoots({bottoms, tops}, std::numeric_limits::max(), p_stopAfter); + solver.FindRoots({bottoms, tops}, p_stopAfter); } catch (const SingularMatrixException &) { p_isSingular = true; diff --git a/src/solvers/enumpoly/enumpoly.h b/src/solvers/enumpoly/enumpoly.h index 60c043f5b..1cd1d4c54 100644 --- a/src/solvers/enumpoly/enumpoly.h +++ b/src/solvers/enumpoly/enumpoly.h @@ -3,7 +3,6 @@ // Copyright (c) 1994-2024, The Gambit Project (http://www.gambit-project.org) // // FILE: src/solvers/enumpoly/enumpoly.h -// // Finds Nash equilibria of a game by solving systems of polynomial equations // by enumerating supports. // diff --git a/src/solvers/enumpoly/gameseq.cc b/src/solvers/enumpoly/gameseq.cc index 34f8064ac..46ba39b1c 100644 --- a/src/solvers/enumpoly/gameseq.cc +++ b/src/solvers/enumpoly/gameseq.cc @@ -2,7 +2,7 @@ // This file is part of Gambit // Copyright (c) 1994-2024, The Gambit Project (http://www.gambit-project.org) // -// FILE: src/tools/enumpoly/sfg.cc +// FILE: src/solvers/enumpoly/sfg.cc // Implementation of sequence form classes // // This program is free software; you can redistribute it and/or modify diff --git a/src/solvers/enumpoly/gameseq.h b/src/solvers/enumpoly/gameseq.h index cc20bd871..ae2dc53ad 100644 --- a/src/solvers/enumpoly/gameseq.h +++ b/src/solvers/enumpoly/gameseq.h @@ -2,7 +2,7 @@ // This file is part of Gambit // Copyright (c) 1994-2024, The Gambit Project (http://www.gambit-project.org) // -// FILE: src/tools/enumpoly/sfg.h +// FILE: src/solvers/enumpoly/gameseq.h // Interface to sequence form classes // // This program is free software; you can redistribute it and/or modify @@ -20,13 +20,10 @@ // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // -#ifndef SFG_H -#define SFG_H - -#include +#ifndef GAMESEQ_H +#define GAMESEQ_H #include "gambit.h" -#include "odometer.h" #include "ndarray.h" namespace Gambit { @@ -290,7 +287,7 @@ class GameSequenceForm { try { return m_constraints.at({p_infoset, p_action}); } - catch (std::out_of_range) { + catch (std::out_of_range &) { return 0; } } @@ -301,4 +298,4 @@ class GameSequenceForm { } // end namespace Gambit -#endif // SFG_H +#endif // GAMESEQ_H diff --git a/src/solvers/enumpoly/gcomplex.cc b/src/solvers/enumpoly/gcomplex.cc deleted file mode 100644 index 016fda390..000000000 --- a/src/solvers/enumpoly/gcomplex.cc +++ /dev/null @@ -1,79 +0,0 @@ -// -// This file is part of Gambit -// Copyright (c) 1994-2024, The Gambit Project (http://www.gambit-project.org) -// -// FILE: src/tools/enumpoly/complex.cc -// Implementation of a complex number 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 - -#include "gambit.h" -#include "gcomplex.h" - -//-------------------------------------------------------------------------- -// class: gComplex -//-------------------------------------------------------------------------- - -//-------------------------------------------------------------------------- -// operators -//-------------------------------------------------------------------------- - -void gComplex::operator/=(const gComplex &y) -{ - if (y == (gComplex)0) { - throw Gambit::ZeroDivideException(); - } - *this = gComplex((re * y.re + im * y.im) / (y.re * y.re + y.im * y.im), - (-re * y.im + im * y.re) / (y.re * y.re + y.im * y.im)); -} - -gComplex gComplex::operator/(const gComplex &y) const -{ - if (y == (gComplex)0) { - throw Gambit::ZeroDivideException(); - } - return {(re * y.re + im * y.im) / (y.re * y.re + y.im * y.im), - (-re * y.im + im * y.re) / (y.re * y.re + y.im * y.im)}; -} - -// FUNCTIONS OUTSIDE THE CLASS - -gComplex pow(const gComplex &x, long y) -{ - if (y < 0) { - if (x == (gComplex)0) { - throw Gambit::AssertionException("Raising 0^0."); - } - gComplex x1((gComplex)1 / x); - return pow(x1, -y); - } - else if (y == 0) { - return gComplex(1); - } - else if (y == 1) { - return x; - } - else { - gComplex sqrt_of_answer = pow(x, y / 2); - gComplex answer = sqrt_of_answer * sqrt_of_answer; - if (y % 2 == 1) { - answer *= x; - } - return answer; - } -} diff --git a/src/solvers/enumpoly/gcomplex.h b/src/solvers/enumpoly/gcomplex.h deleted file mode 100644 index 313ec2976..000000000 --- a/src/solvers/enumpoly/gcomplex.h +++ /dev/null @@ -1,85 +0,0 @@ -// -// This file is part of Gambit -// Copyright (c) 1994-2024, The Gambit Project (http://www.gambit-project.org) -// -// FILE: src/tools/enumpoly/complex.h -// Declaration of a complex number 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 GCOMPLEX_H -#define GCOMPLEX_H - -#include - -class gComplex { -protected: - double re; - double im; - -public: - // Constructors, and the destructor - gComplex() : re(0.0), im(0.0) {} - gComplex(double p_re, double p_im) : re(p_re), im(p_im) {} - gComplex(const gComplex &) = default; - explicit gComplex(int n) : re(n), im(0.0) {} - - ~gComplex() = default; - - // Operators - double RealPart() const { return re; } - double ImaginaryPart() const { return im; } - - gComplex &operator=(const gComplex &y) = default; - - bool operator==(const gComplex &y) const { return (re == y.re && im == y.im); } - bool operator!=(const gComplex &y) const { return (re != y.re || im != y.im); } - - gComplex operator+(const gComplex &y) const { return {re + y.re, im + y.im}; } - gComplex operator-(const gComplex &y) const { return {re - y.re, im - y.im}; } - gComplex operator*(const gComplex &y) const - { - return {re * y.re - im * y.im, re * y.im + im * y.re}; - } - gComplex operator/(const gComplex &y) const; - - gComplex operator-() const { return {-re, -im}; } - - void operator+=(const gComplex &y) - { - re += y.re; - im += y.im; - } - void operator-=(const gComplex &y) - { - re -= y.re; - im -= y.im; - } - void operator*=(const gComplex &y) - { - re = re * y.re - im * y.im; - im = re * y.im + im * y.re; - } - void operator/=(const gComplex &y); - - // friends outside the class - friend double fabs(const gComplex &x); - friend gComplex pow(const gComplex &x, long y); -}; - -inline double fabs(const gComplex &x) { return sqrt(x.re * x.re + x.im * x.im); } - -#endif // GCOMPLEX_H diff --git a/src/solvers/enumpoly/gpartltr.cc b/src/solvers/enumpoly/gpartltr.cc deleted file mode 100644 index aeeaf6a89..000000000 --- a/src/solvers/enumpoly/gpartltr.cc +++ /dev/null @@ -1,50 +0,0 @@ -// -// This file is part of Gambit -// Copyright (c) 1994-2024, The Gambit Project (http://www.gambit-project.org) -// -// FILE: src/tools/enumpoly/gpartltr.cc -// Instantiations of tree of partials classes -// -// This program is free software; you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation; either version 2 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -// - -#include "gpartltr.imp" - -template class TreeOfPartials; -template class ListOfPartialTrees; - -// template class TreeOfPartials; -// template class ListOfPartialTrees; - -// template class TreeOfPartials; -// template gOutput &operator<<(gOutput &f, const TreeOfPartials &y); -// template class Gambit::List >; -// template gOutput &operator<<(gOutput &f, -// const Gambit::List > &y); -// template class Gambit::List > >; -// template gOutput &operator<<(gOutput &f, -// const Gambit::List > > &y); -// template class Gambit::List > > >; -// template class ListOfPartialTrees; -// template gOutput &operator<<(gOutput &f, -// const ListOfPartialTrees &y); - -#include "gtree.imp" - -// template class gTreeNode >; -// template class gTree >; - -template class gTreeNode>; -template class gTree>; diff --git a/src/solvers/enumpoly/gpartltr.h b/src/solvers/enumpoly/gpartltr.h deleted file mode 100644 index c2272823e..000000000 --- a/src/solvers/enumpoly/gpartltr.h +++ /dev/null @@ -1,118 +0,0 @@ -// -// This file is part of Gambit -// Copyright (c) 1994-2024, The Gambit Project (http://www.gambit-project.org) -// -// FILE: src/tools/enumpoly/gpartltr.h -// Interface to TreeOfPartials -// -// 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 GPARTLTR_H -#define GPARTLTR_H - -#include "gambit.h" -#include "gtree.h" -#include "rectangle.h" -#include "gpoly.h" -#include "gpolylst.h" - -// **************************** -// class TreeOfPartials -// **************************** - -template class TreeOfPartials { -private: - gTree> PartialTree; - - /// Recursive Constructions and Computations /// - - void TreeOfPartialsRECURSIVE(gTree> &, gTreeNode> *) const; - - T MaximalNonconstantContributionRECURSIVE(const gTreeNode> *, const Gambit::Vector &, - const Gambit::Vector &, - Gambit::Vector &) const; - - T MaximalNonconstantDifferenceRECURSIVE(const gTreeNode> *, const gTreeNode> *, - const Gambit::Vector &, const Gambit::Vector &, - Gambit::Vector &) const; - -public: - explicit TreeOfPartials(const gPoly &); - TreeOfPartials(const TreeOfPartials &); - ~TreeOfPartials(); - - inline bool operator==(const TreeOfPartials &rhs) const - { - return (PartialTree == rhs.PartialTree); - } - inline bool operator!=(const TreeOfPartials &rhs) const { return !(*this == rhs); } - inline int Dmnsn() const { return RootNode()->GetData().Dmnsn(); } - T EvaluateRootPoly(const Gambit::Vector &point) const; - - T MaximalNonconstantContribution(const Gambit::Vector &, const Gambit::Vector &) const; - T MaximalNonconstantDifference(const TreeOfPartials &, const Gambit::Vector &, - const Gambit::Vector &) const; - - inline gTreeNode> *RootNode() const { return PartialTree.RootNode(); } - inline gPoly RootPoly() const { return RootNode()->GetData(); } - T ValueOfRootPoly(const Gambit::Vector &point) const; - T ValueOfPartialOfRootPoly(const int &, const Gambit::Vector &) const; - Gambit::Vector VectorOfPartials(const Gambit::Vector &) const; - bool PolyHasNoRootsIn(const Rectangle &) const; - bool MultiaffinePolyHasNoRootsIn(const Rectangle &) const; - bool PolyEverywhereNegativeIn(const Rectangle &) const; - bool MultiaffinePolyEverywhereNegativeIn(const Rectangle &) const; - - // friend gOutput& operator << (gOutput& output, const TreeOfPartials& x); -}; - -// ********************************* -// class ListOfPartialTrees -// ********************************* - -template class ListOfPartialTrees { -private: - Gambit::List> PartialTreeList; - - // Disabling this operator -- we don't want it called - ListOfPartialTrees &operator=(const ListOfPartialTrees &); - -public: - explicit ListOfPartialTrees(const Gambit::List> &); - explicit ListOfPartialTrees(const gPolyList &); - ListOfPartialTrees(const ListOfPartialTrees &); - ~ListOfPartialTrees(); - - // operators - bool operator==(const ListOfPartialTrees &) const; - bool operator!=(const ListOfPartialTrees &) const; - - const TreeOfPartials &operator[](int i) const { return PartialTreeList[i]; } - - // Information - int Length() const { return PartialTreeList.size(); } - int Dmnsn() const { return PartialTreeList.front().Dmnsn(); } - Gambit::Matrix DerivativeMatrix(const Gambit::Vector &) const; - Gambit::Matrix DerivativeMatrix(const Gambit::Vector &, const int &) const; - Gambit::SquareMatrix SquareDerivativeMatrix(const Gambit::Vector &) const; - Gambit::Vector ValuesOfRootPolys(const Gambit::Vector &, const int &) const; - T MaximalNonconstantDifference(const int &, const int &, const Gambit::Vector &, - const Gambit::Vector &) const; - - // friend gOutput& operator << (gOutput& output, const ListOfPartialTrees& x); -}; - -#endif // GPARTLTR_H diff --git a/src/solvers/enumpoly/gpartltr.imp b/src/solvers/enumpoly/gpartltr.imp deleted file mode 100644 index c42f12224..000000000 --- a/src/solvers/enumpoly/gpartltr.imp +++ /dev/null @@ -1,502 +0,0 @@ -// -// This file is part of Gambit -// Copyright (c) 1994-2024, The Gambit Project (http://www.gambit-project.org) -// -// FILE: src/tools/enumpoly/gpartltr.imp -// Implementation of TreeOfPartials -// -// 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 "gpartltr.h" - -//--------------------------------------------------------------- -// class: TreeOfPartials -//--------------------------------------------------------------- - -//--------------------------- -// Constructors / Destructors -//--------------------------- - -template TreeOfPartials::TreeOfPartials(const gPoly &given) : PartialTree(given) -{ - TreeOfPartialsRECURSIVE(PartialTree, PartialTree.RootNode()); -} - -//------------------------------------------------------------------------- -// Recursive generation of all partial derivatives of the root polynomial -//------------------------------------------------------------------------- - -template -void TreeOfPartials::TreeOfPartialsRECURSIVE(gTree> &t, gTreeNode> *n) const -{ - if (n->GetData().Degree() >= 1) { - for (int i = 1; i <= n->GetData().Dmnsn(); i++) { - t.InsertAt(n->GetData().PartialDerivative(i), n); - TreeOfPartialsRECURSIVE(t, n->GetYoungest()); - } - } -} - -template -TreeOfPartials::TreeOfPartials(const TreeOfPartials &qs) : PartialTree(qs.PartialTree) -{ -} - -template TreeOfPartials::~TreeOfPartials() = default; - -template -T TreeOfPartials::ValueOfPartialOfRootPoly(const int &coord, const Gambit::Vector &p) const -{ - if (RootPoly().Degree() <= 0) { - return (T)0; - } - else { - int i = 1; - gTreeNode> *node = RootNode()->GetEldest(); - while (i < coord) { - i++; - node = node->GetNext(); - } - T answer = node->GetData().Evaluate(p); - return answer; - } -} - -template -Gambit::Vector TreeOfPartials::VectorOfPartials(const Gambit::Vector &point) const -{ - Gambit::Vector answer(Dmnsn()); - for (int i = 1; i <= Dmnsn(); i++) { - answer[i] = ValueOfPartialOfRootPoly(i, point); - } - return answer; -} - -template -T TreeOfPartials::MaximalNonconstantContributionRECURSIVE( - const gTreeNode> *n, const Gambit::Vector &p, - const Gambit::Vector &halvesoflengths, Gambit::Vector &wrtos) const -{ - T answer = (T)0; - - if (n->GetEldest() != nullptr) { - Gambit::List> *> children = PartialTree.Children(n); - for (size_t i = 1; i <= children.size(); i++) { - wrtos[i]++; - - T increment = children[i]->GetData().Evaluate(p); - if (increment < (T)0) { - increment = -increment; - } - - for (int j = 1; j <= p.size(); j++) { - for (int k = 1; k <= wrtos[j]; k++) { - increment *= halvesoflengths[j]; - increment /= (T)k; - } - } - - answer += increment; - - answer += MaximalNonconstantContributionRECURSIVE(children[i], p, halvesoflengths, wrtos); - - wrtos[i]--; - } - } - - return answer; -} - -template -T TreeOfPartials::MaximalNonconstantDifferenceRECURSIVE( - const gTreeNode> *n1, const gTreeNode> *n2, const Gambit::Vector &p, - const Gambit::Vector &halvesoflengths, Gambit::Vector &wrtos) const -{ - T answer = (T)0; - - if (n1->GetEldest() != nullptr && n2->GetEldest() != nullptr) { - Gambit::List> *> children1 = PartialTree.Children(n1); - Gambit::List> *> children2 = PartialTree.Children(n2); - for (size_t i = 1; i <= children1.size(); i++) { - wrtos[i]++; - - T increment = children1[i]->GetData().Evaluate(p) - children2[i]->GetData().Evaluate(p); - if (increment < (T)0) { - increment = -increment; - } - - for (int j = 1; j <= p.size(); j++) { - for (int k = 1; k <= wrtos[j]; k++) { - increment *= halvesoflengths[j]; - increment /= (T)k; - } - } - - answer += increment; - - answer += MaximalNonconstantDifferenceRECURSIVE(children1[i], children2[i], p, - halvesoflengths, wrtos); - - wrtos[i]--; - } - } - - else if (n1->GetEldest() != nullptr && n2->GetEldest() == nullptr) { - auto children1 = PartialTree.Children(n1); - for (size_t i = 1; i <= children1.size(); i++) { - wrtos[i]++; - - T increment = children1[i]->GetData().Evaluate(p); - if (increment < (T)0) { - increment = -increment; - } - - for (int j = 1; j <= p.size(); j++) { - for (int k = 1; k <= wrtos[j]; k++) { - increment *= halvesoflengths[j]; - increment /= (T)k; - } - } - - answer += increment; - - answer += MaximalNonconstantContributionRECURSIVE(children1[i], p, halvesoflengths, wrtos); - - wrtos[i]--; - } - } - - else if (n1->GetEldest() == nullptr && n2->GetEldest() != nullptr) { - auto children2 = PartialTree.Children(n2); - for (size_t i = 1; i <= children2.size(); i++) { - wrtos[i]++; - - T increment = children2[i]->GetData().Evaluate(p); - if (increment < (T)0) { - increment = -increment; - } - - for (int j = 1; j <= p.size(); j++) { - for (int k = 1; k <= wrtos[j]; k++) { - increment *= halvesoflengths[j]; - increment /= (T)k; - } - } - - answer += increment; - - answer += MaximalNonconstantContributionRECURSIVE(children2[i], p, halvesoflengths, wrtos); - - wrtos[i]--; - } - } - - return answer; -} - -template -T TreeOfPartials::MaximalNonconstantContribution(const Gambit::Vector &p, - const Gambit::Vector &halvesoflengths) const -{ - Gambit::Vector WithRespectTos(p.size()); - for (int i = 1; i <= p.size(); i++) { - WithRespectTos[i] = 0; - } - - return MaximalNonconstantContributionRECURSIVE(RootNode(), p, halvesoflengths, WithRespectTos); -} - -template -T TreeOfPartials::MaximalNonconstantDifference(const TreeOfPartials &other, - const Gambit::Vector &p, - const Gambit::Vector &halvesoflengths) const -{ - Gambit::Vector WithRespectTos(p.size()); - for (int i = 1; i <= p.size(); i++) { - WithRespectTos[i] = 0; - } - - return MaximalNonconstantDifferenceRECURSIVE(other.RootNode(), RootNode(), p, halvesoflengths, - WithRespectTos); -} - -template T TreeOfPartials::EvaluateRootPoly(const Gambit::Vector &point) const -{ - return RootNode()->GetData().Evaluate(point); -} - -template T TreeOfPartials::ValueOfRootPoly(const Gambit::Vector &point) const -{ - return RootPoly().Evaluate(point); -} - -template bool TreeOfPartials::PolyHasNoRootsIn(const Rectangle &r) const -{ - if (this->RootNode()->GetData().IsMultiaffine()) { - return MultiaffinePolyHasNoRootsIn(r); - } - else { - Gambit::Vector center = r.Center(); - - T constant = this->RootNode()->GetData().Evaluate(center); - if (constant < (T)0) { - constant = -constant; - } - - Gambit::Vector HalvesOfSideLengths = r.SideLengths(); - for (int k = 1; k <= Dmnsn(); k++) { - HalvesOfSideLengths[k] /= (T)2; - } - - T max = this->MaximalNonconstantContribution(center, HalvesOfSideLengths); - - if (max >= constant) { - return false; - } - else { - return true; - } - } -} - -template bool TreeOfPartials::MultiaffinePolyHasNoRootsIn(const Rectangle &r) const -{ - int sign; - if (this->RootNode()->GetData().Evaluate(r.Center()) > (T)0) { - sign = 1; - } - else { - sign = -1; - } - - Gambit::Array zeros(Dmnsn()); - Gambit::Array ones(Dmnsn()); - for (int j = 1; j <= Dmnsn(); j++) { - zeros[j] = 0; - if (this->RootNode()->GetData().DegreeOfVar(j) > 0) { - ones[j] = 1; - } - // Equation_i_uses_var_j(index,j)) ones[j] = 1; - else { - ones[j] = 0; - } - } - gIndexOdometer topbottoms(zeros, ones); - - while (topbottoms.Turn()) { - Gambit::Vector point(Dmnsn()); - for (int i = 1; i <= Dmnsn(); i++) { - if (topbottoms[i] == 0) { - point[i] = r.Side(i).LowerBound(); - } - else { - point[i] = r.Side(i).UpperBound(); - } - } - if ((T)sign * this->RootNode()->GetData().Evaluate(point) <= (T)0) { - return false; - } - } - - return true; -} - -template -bool TreeOfPartials::MultiaffinePolyEverywhereNegativeIn(const Rectangle &r) const -{ - if (Dmnsn() == 0) { - Gambit::Vector point(Dmnsn()); - if (this->RootNode()->GetData().Evaluate(point) >= (T)0) { - return false; - } - else { - return true; - } - } - - Gambit::Array zeros(Dmnsn()); - Gambit::Array ones(Dmnsn()); - for (int j = 1; j <= Dmnsn(); j++) { - zeros[j] = 0; - if (this->RootNode()->GetData().DegreeOfVar(j) > 0) { - ones[j] = 1; - } - else { - ones[j] = 0; - } - } - gIndexOdometer topbottoms(zeros, ones); - - while (topbottoms.Turn()) { - Gambit::Vector point(Dmnsn()); - for (int i = 1; i <= Dmnsn(); i++) { - if (topbottoms[i] == 0) { - point[i] = r.Side(i).LowerBound(); - } - else { - point[i] = r.Side(i).UpperBound(); - } - } - if (this->RootNode()->GetData().Evaluate(point) >= (T)0) { - return false; - } - } - - return true; -} - -template bool TreeOfPartials::PolyEverywhereNegativeIn(const Rectangle &r) const -{ - if (this->RootNode()->GetData().IsMultiaffine()) { - return MultiaffinePolyEverywhereNegativeIn(r); - } - else { - Gambit::Vector center = r.Center(); - - T constant = this->RootNode()->GetData().Evaluate(center); - if (constant >= (T)0) { - return false; - } - - Gambit::Vector HalvesOfSideLengths = r.SideLengths(); - for (int k = 1; k <= Dmnsn(); k++) { - HalvesOfSideLengths[k] /= (T)2; - } - - T max = this->MaximalNonconstantContribution(center, HalvesOfSideLengths); - - if (max + constant >= (T)0) { - return false; - } - else { - return true; - } - } -} - -//--------------------------------------------------------------- -// class: ListOfPartialTrees -//--------------------------------------------------------------- - -//--------------------------- -// Constructors / Destructors -//--------------------------- - -template ListOfPartialTrees::ListOfPartialTrees(const Gambit::List> &given) -{ - for (const auto &poly : given) { - PartialTreeList.push_back(TreeOfPartials(poly)); - } -} - -template ListOfPartialTrees::ListOfPartialTrees(const gPolyList &given) -{ - for (int i = 1; i <= given.Length(); i++) { - PartialTreeList.push_back(TreeOfPartials(given[i])); - } -} - -template -ListOfPartialTrees::ListOfPartialTrees(const ListOfPartialTrees &qs) - : PartialTreeList(qs.PartialTreeList) -{ -} - -template ListOfPartialTrees::~ListOfPartialTrees() = default; - -template bool ListOfPartialTrees::operator==(const ListOfPartialTrees &rhs) const -{ - if (Length() != rhs.Length()) { - return false; - } - for (int i = 1; i <= Length(); i++) { - if ((*this)[i] != rhs[i]) { - return false; - } - } - return true; -} - -template bool ListOfPartialTrees::operator!=(const ListOfPartialTrees &rhs) const -{ - return !(*this == rhs); -} - -template -Gambit::Matrix ListOfPartialTrees::DerivativeMatrix(const Gambit::Vector &p) const -{ - Gambit::Matrix answer(Length(), Dmnsn()); - int i; - for (i = 1; i <= Length(); i++) { - for (int j = 1; j <= Dmnsn(); j++) { - answer(i, j) = (*this)[i].ValueOfPartialOfRootPoly(j, p); - } - } - - return answer; -} - -template -Gambit::Matrix ListOfPartialTrees::DerivativeMatrix(const Gambit::Vector &p, - const int &NoEquations) const -{ - Gambit::Matrix answer(NoEquations, Dmnsn()); - int i; - for (i = 1; i <= NoEquations; i++) { - for (int j = 1; j <= Dmnsn(); j++) { - answer(i, j) = (*this)[i].ValueOfPartialOfRootPoly(j, p); - } - } - - return answer; -} - -template -Gambit::SquareMatrix -ListOfPartialTrees::SquareDerivativeMatrix(const Gambit::Vector &p) const -{ - // assert (Length() >= Dmnsn()); - Gambit::SquareMatrix answer(Dmnsn()); - int i; - for (i = 1; i <= Dmnsn(); i++) { - for (int j = 1; j <= Dmnsn(); j++) { - answer(i, j) = (*this)[i].ValueOfPartialOfRootPoly(j, p); - } - } - - return answer; -} - -template -Gambit::Vector ListOfPartialTrees::ValuesOfRootPolys(const Gambit::Vector &point, - const int &NoEquations) const -{ - Gambit::Vector answer(NoEquations); - for (int i = 1; i <= NoEquations; i++) { - answer[i] = PartialTreeList[i].EvaluateRootPoly(point); - } - - return answer; -} - -template -T ListOfPartialTrees::MaximalNonconstantDifference( - const int &i, const int &j, const Gambit::Vector &point, - const Gambit::Vector &halvesoflengths) const -{ - return PartialTreeList[i].MaximalNonconstantDifference(PartialTreeList[j], point, - halvesoflengths); -} diff --git a/src/solvers/enumpoly/gpoly.h b/src/solvers/enumpoly/gpoly.h deleted file mode 100644 index 47d9bbb38..000000000 --- a/src/solvers/enumpoly/gpoly.h +++ /dev/null @@ -1,376 +0,0 @@ -// -// This file is part of Gambit -// Copyright (c) 1994-2024, The Gambit Project (http://www.gambit-project.org) -// -// FILE: src/tools/enumpoly/gpoly.h -// Declaration of multivariate polynomial type -// -// 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 GPOLY_H -#define GPOLY_H - -#include -#include "gambit.h" -#include "core/sqmatrix.h" - -using namespace Gambit; - -class VariableSpace { -public: - struct Variable { - std::string name; - int number; - }; - - explicit VariableSpace(size_t nvars) - { - for (size_t i = 1; i <= nvars; i++) { - m_variables.push_back({"n" + std::to_string(i), static_cast(i)}); - } - } - VariableSpace(const VariableSpace &) = delete; - ~VariableSpace() = default; - VariableSpace &operator=(const VariableSpace &) = delete; - const Variable &operator[](const int index) const { return m_variables[index]; } - int Dmnsn() const { return m_variables.size(); } - -private: - Array m_variables; -}; - -// An exponent vector is a vector of integers representing the exponents on variables in -// a space in a monomial. -// -// Exponent vectors are ordered lexicographically. -class ExponentVector { -private: - const VariableSpace *m_space; - Vector m_components; - -public: - explicit ExponentVector(const VariableSpace *p) : m_space(p), m_components(p->Dmnsn()) - { - m_components = 0; - } - // Construct x_i^j - ExponentVector(const VariableSpace *p, const int i, const int j) - : m_space(p), m_components(p->Dmnsn()) - { - m_components = 0; - m_components[i] = j; - } - ExponentVector(const VariableSpace *space, const Vector &exps) - : m_space(space), m_components(exps) - { - } - ExponentVector(const ExponentVector &) = default; - ~ExponentVector() = default; - - // Operators - ExponentVector &operator=(const ExponentVector &) = default; - - int operator[](int index) const { return m_components[index]; } - - bool operator==(const ExponentVector &y) const - { - return m_space == y.m_space && m_components == y.m_components; - } - bool operator!=(const ExponentVector &y) const - { - return m_space != y.m_space || m_components != y.m_components; - } - ExponentVector operator+(const ExponentVector &v) const - { - ExponentVector tmp(*this); - tmp.m_components += v.m_components; - return tmp; - } - - // Other operations - ExponentVector WithZeroExponent(const int varnumber) const - { - ExponentVector tmp(*this); - tmp.m_components[varnumber] = 0; - return tmp; - } - ExponentVector WithDecrementedExponent(const int varnumber) const - { - ExponentVector tmp(*this); - tmp.m_components[varnumber]--; - return tmp; - } - - // Information - int Dmnsn() const { return m_space->Dmnsn(); } - bool IsConstant() const - { - for (int i = 1; i <= Dmnsn(); i++) { - if ((*this)[i] > 0) { - return false; - } - } - return true; - } - bool IsMultiaffine() const - { - for (int i = 1; i <= Dmnsn(); i++) { - if ((*this)[i] > 1) { - return false; - } - } - return true; - } - int TotalDegree() const - { - int exp_sum = 0; - for (int i = 1; i <= Dmnsn(); i++) { - exp_sum += (*this)[i]; - } - return exp_sum; - } - - // Manipulation - void ToZero() - { - for (int i = 1; i <= Dmnsn(); i++) { - m_components[i] = 0; - } - } - - bool operator<(const ExponentVector &y) const - { - for (int i = 1; i <= Dmnsn(); i++) { - if (m_components[i] < y.m_components[i]) { - return true; - } - if (m_components[i] > y.m_components[i]) { - return false; - } - } - return false; - } - bool operator<=(const ExponentVector &y) const { return *this < y || *this == y; } - bool operator>(const ExponentVector &y) const { return !(*this <= y); } - bool operator>=(const ExponentVector &y) const { return !(*this < y); } -}; - -/// A monomial of multiple variables with non-negative exponents -template class gMono { -private: - T coef; - ExponentVector exps; - -public: - // constructors - gMono(const VariableSpace *p, const T &x) : coef(x), exps(p) {} - gMono(const T &x, const ExponentVector &e) : coef(x), exps(e) - { - if (x == static_cast(0)) { - exps.ToZero(); - } - } - gMono(const gMono &) = default; - ~gMono() = default; - - // operators - gMono &operator=(const gMono &) = default; - - bool operator==(const gMono &y) const { return (coef == y.coef && exps == y.exps); } - bool operator!=(const gMono &y) const { return (coef != y.coef || exps != y.exps); } - gMono operator*(const gMono &y) const { return {coef * y.coef, exps + y.exps}; } - gMono operator+(const gMono &y) const { return {coef + y.coef, exps}; } - gMono &operator+=(const gMono &y) - { - coef += y.coef; - return *this; - } - gMono &operator*=(const T &v) - { - coef *= v; - return *this; - } - gMono operator-() const { return {-coef, exps}; } - - // information - const T &Coef() const { return coef; } - int Dmnsn() const { return exps.Dmnsn(); } - int TotalDegree() const { return exps.TotalDegree(); } - bool IsConstant() const { return exps.IsConstant(); } - bool IsMultiaffine() const { return exps.IsMultiaffine(); } - const ExponentVector &ExpV() const { return exps; } - T Evaluate(const Vector &vals) const - { - T answer = coef; - for (int i = 1; i <= exps.Dmnsn(); i++) { - for (int j = 1; j <= exps[i]; j++) { - answer *= vals[i]; - } - } - return answer; - } -}; - -// A multivariate polynomial -template class gPoly { -private: - const VariableSpace *Space; - List> Terms; - - // Arithmetic - List> Adder(const List> &, const List> &) const; - List> Mult(const List> &, const List> &) const; - gPoly DivideByPolynomial(const gPoly &den) const; - - gPoly TranslateOfMono(const gMono &, const Vector &) const; - gPoly MonoInNewCoordinates(const gMono &, const SquareMatrix &) const; - -public: - gPoly(const VariableSpace *p) : Space(p) {} - // Constructs a constant gPoly - gPoly(const VariableSpace *p, const T &constant) : Space(p) - { - if (constant != static_cast(0)) { - Terms.push_back(gMono(p, constant)); - } - } - gPoly(const gPoly &) = default; - // Constructs a gPoly that is x_{var_no}^exp; - gPoly(const VariableSpace *p, const int var_no, const int exp) : Space(p) - { - Terms.push_back(gMono(static_cast(1), ExponentVector(p, var_no, exp))); - } - // Constructs a gPoly that is the monomial coeff*vars^exps; - gPoly(const VariableSpace *p, const ExponentVector &exps, const T &coeff) : Space(p) - { - Terms.push_back(gMono(coeff, exps)); - } - // Constructs a gPoly with single monomial - gPoly(const VariableSpace *p, const gMono &mono) : Space(p) { Terms.push_back(mono); } - ~gPoly() = default; - - //---------- - // Operators: - //---------- - - gPoly &operator=(const gPoly &) = default; - gPoly operator-() const - { - gPoly neg(*this); - for (size_t j = 1; j <= Terms.size(); j++) { - neg.Terms[j] = -Terms[j]; - } - return neg; - } - gPoly operator-(const gPoly &p) const - { - gPoly dif(*this); - dif -= p; - return dif; - } - void operator-=(const gPoly &p) - { - gPoly neg = p; - for (size_t i = 1; i <= neg.Terms.size(); i++) { - neg.Terms[i] = -neg.Terms[i]; - } - Terms = Adder(Terms, neg.Terms); - } - gPoly operator+(const gPoly &p) const - { - gPoly sum(*this); - sum += p; - return sum; - } - gPoly operator+(const T &v) const - { - gPoly result(*this); - result += v; - return result; - } - void operator+=(const gPoly &p) { Terms = Adder(Terms, p.Terms); } - void operator+=(const T &val) { *this += gPoly(Space, val); } - gPoly operator*(const gPoly &p) const - { - gPoly prod(*this); - prod *= p; - return prod; - } - gPoly operator*(const T &v) const - { - gPoly result(*this); - result *= v; - return result; - } - void operator*=(const gPoly &p) { Terms = Mult(Terms, p.Terms); } - void operator*=(const T &val) - { - for (size_t j = 1; j <= Terms.size(); j++) { - Terms[j] *= val; - } - } - gPoly operator/(const T &val) const - { - if (val == static_cast(0)) { - throw ZeroDivideException(); - } - return (*this) * (static_cast(1) / val); - } - gPoly operator/(const gPoly &den) const { return DivideByPolynomial(den); } - - bool operator==(const gPoly &p) const { return Space == p.Space && Terms == p.Terms; } - bool operator!=(const gPoly &p) const { return Space != p.Space || Terms != p.Terms; } - - //------------- - // Information - //------------- - - const VariableSpace *GetSpace() const { return Space; } - int Dmnsn() const { return Space->Dmnsn(); } - int DegreeOfVar(int var_no) const - { - return std::accumulate(Terms.begin(), Terms.end(), 0, [&var_no](int v, const gMono &m) { - return std::max(v, m.ExpV()[var_no]); - }); - } - int Degree() const - { - return std::accumulate(Terms.begin(), Terms.end(), 0, - [](int v, const gMono &m) { return std::max(v, m.TotalDegree()); }); - } - gPoly LeadingCoefficient(int varnumber) const; - T NumLeadCoeff() const { return (Terms.size() == 1) ? Terms.front().Coef() : static_cast(0); } - bool IsMultiaffine() const - { - return std::all_of(Terms.begin(), Terms.end(), - [](const gMono &t) { return t.IsMultiaffine(); }); - } - T Evaluate(const Vector &values) const - { - return std::accumulate( - Terms.begin(), Terms.end(), static_cast(0), - [&values](const T &v, const gMono &m) { return v + m.Evaluate(values); }); - } - gPoly PartialDerivative(int varnumber) const; - const List> &MonomialList() const { return Terms; } - - gPoly TranslateOfPoly(const Vector &) const; - gPoly PolyInNewCoordinates(const SquareMatrix &) const; - T MaximalValueOfNonlinearPart(const T &) const; - gPoly Normalize() const; -}; - -#endif // # GPOLY_H diff --git a/src/solvers/enumpoly/gpolylst.cc b/src/solvers/enumpoly/gpolylst.cc deleted file mode 100644 index b0d76cd89..000000000 --- a/src/solvers/enumpoly/gpolylst.cc +++ /dev/null @@ -1,37 +0,0 @@ -// -// This file is part of Gambit -// Copyright (c) 1994-2024, The Gambit Project (http://www.gambit-project.org) -// -// FILE: src/tools/enumpoly/gpolylst.cc -// Instantiations of polynomial list 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 "gpolylst.imp" -#include "core/matrix.imp" - -// template class gPolyList; -// template gOutput &operator<<(gOutput &f, const gPolyList &y); - -// template class gPolyList; - -// template class gPolyList; -// template gOutput &operator<<(gOutput &f, const gPolyList &y); - -template class gPolyList; - -template class Gambit::RectArray *>; -// template class Gambit::RectArray*>; diff --git a/src/solvers/enumpoly/gpolylst.h b/src/solvers/enumpoly/gpolylst.h deleted file mode 100644 index 87a98e20f..000000000 --- a/src/solvers/enumpoly/gpolylst.h +++ /dev/null @@ -1,72 +0,0 @@ -// -// This file is part of Gambit -// Copyright (c) 1994-2024, The Gambit Project (http://www.gambit-project.org) -// -// FILE: src/tools/enumpoly/gpolylist.h -// Declaration of polynomial list type -// -// 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 GPOLYLST_H -#define GPOLYLST_H - -#include "odometer.h" -#include "core/sqmatrix.h" -#include "gpoly.h" - -template class gPolyList { -private: - const VariableSpace *Space; - Gambit::List *> List; - -public: - gPolyList(const VariableSpace *sp) : Space(sp) {} - - gPolyList(const VariableSpace *, const Gambit::List *> &); - gPolyList(const VariableSpace *, const Gambit::List> &); - gPolyList(const gPolyList &); - - ~gPolyList(); // Deletes all pointees - - // Operators - gPolyList &operator=(const gPolyList &); - - bool operator==(const gPolyList &) const; - bool operator!=(const gPolyList &x) const { return !(*this == x); } - void operator+=(const gPoly &); - void operator+=(const gPolyList &); - - // Takes ownership of pointer - void operator+=(gPoly *poly) { List.push_back(poly); } - - const gPoly &operator[](const int index) const { return *(List[index]); } - - // New Coordinate Systems - gPolyList TranslateOfSystem(const Gambit::Vector &) const; - gPolyList SystemInNewCoordinates(const Gambit::SquareMatrix &) const; - - // Information - const VariableSpace *AmbientSpace() const { return Space; } - int Length() const { return List.size(); } - int Dmnsn() const { return Space->Dmnsn(); } - bool IsMultiaffine() const; - Gambit::Vector Evaluate(const Gambit::Vector &) const; - - // Conversion - Gambit::List> NormalizedList() const; -}; - -#endif // GPOLYLST_H diff --git a/src/solvers/enumpoly/gpolylst.imp b/src/solvers/enumpoly/gpolylst.imp deleted file mode 100644 index 3fa88b7ac..000000000 --- a/src/solvers/enumpoly/gpolylst.imp +++ /dev/null @@ -1,182 +0,0 @@ -// -// This file is part of Gambit -// Copyright (c) 1994-2024, The Gambit Project (http://www.gambit-project.org) -// -// FILE: src/tools/enumpoly/gpolylst.imp -// Implementation of polynomial list type -// -// 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 "gpolylst.h" - -template -Gambit::List InteriorSegment(const Gambit::List &p_list, int first, int last) -{ - Gambit::List answer; - - for (int i = first; i <= last; i++) { - answer.push_back(p_list[i]); - } - - return answer; -} - -//--------------------------------------------------------------- -// gPolyList -//--------------------------------------------------------------- - -//--------------------------- -// Constructors / Destructors -//--------------------------- - -template -gPolyList::gPolyList(const VariableSpace *sp, const Gambit::List *> &plist) : Space(sp) -{ - for (size_t ii = 1; ii <= plist.size(); ii++) { - auto *temp = new gPoly(*plist[ii]); - List.push_back(temp); - } -} - -template -gPolyList::gPolyList(const VariableSpace *sp, const Gambit::List> &list) : Space(sp) -{ - for (size_t ii = 1; ii <= list.size(); ii++) { - auto *temp = new gPoly(list[ii]); - List.push_back(temp); - } -} - -template gPolyList::gPolyList(const gPolyList &lst) : Space(lst.Space) -{ - for (size_t ii = 1; ii <= lst.List.size(); ii++) { - auto *temp = new gPoly(*(lst.List[ii])); - List.push_back(temp); - } -} - -template gPolyList::~gPolyList() -{ - for (size_t ii = 1; ii <= List.size(); ii++) { - delete List[ii]; - } -} - -//---------------------------------- -// Operators -//---------------------------------- - -template gPolyList &gPolyList::operator=(const gPolyList &rhs) -{ - if (*this != rhs) { - for (size_t ii = List.size(); ii >= 1; ii--) { - delete List[ii]; - List.erase(std::next(List.begin(), ii - 1)); - } - - for (size_t ii = 1; ii <= rhs.List.size(); ii++) { - auto *temp = new gPoly(*(rhs.List[ii])); - List.push_back(temp); - } - } - return *this; -} - -template bool gPolyList::operator==(const gPolyList &rhs) const -{ - if (Space != rhs.Space) { - return false; - } - if (List.size() != rhs.List.size()) { - return false; - } - for (size_t j = 1; j <= List.size(); j++) { - if (*List[j] != *(rhs.List[j])) { - return false; - } - } - return true; -} - -template void gPolyList::operator+=(const gPoly &new_poly) -{ - auto *temp = new gPoly(new_poly); - List.push_back(temp); -} - -template void gPolyList::operator+=(const gPolyList &new_list) -{ - for (int i = 1; i <= new_list.Length(); i++) { - List.push_back(new gPoly(new_list[i])); - } -} - -//------------------------------------------ -// New Coordinate Systems -//------------------------------------------ - -template -gPolyList gPolyList::TranslateOfSystem(const Gambit::Vector &new_origin) const -{ - Gambit::List> new_polys; - for (int i = 1; i <= Length(); i++) { - new_polys.push_back((*this)[i].TranslateOfPoly(new_origin)); - } - return gPolyList(AmbientSpace(), new_polys); -} - -template -gPolyList gPolyList::SystemInNewCoordinates(const Gambit::SquareMatrix &M) const -{ - Gambit::List> new_polys; - for (int i = 1; i <= Length(); i++) { - new_polys.push_back((*this)[i].PolyInNewCoordinates(M)); - } - return gPolyList(AmbientSpace(), new_polys); -} - -//---------------------------------- -// Information -//---------------------------------- - -template bool gPolyList::IsMultiaffine() const -{ - for (const auto &v : List) { - if (!(*v).IsMultiaffine()) { - return false; - } - } - return true; -} - -template Gambit::Vector gPolyList::Evaluate(const Gambit::Vector &v) const -{ - Gambit::Vector answer(Length()); - for (size_t ii = 1; ii <= List.size(); ii++) { - answer[ii] = List[ii]->Evaluate(v); - } - - return answer; -} - -template Gambit::List> gPolyList::NormalizedList() const -{ - Gambit::List> newlist; - for (const auto &v : List) { - newlist.push_back(v->Normalize()); - } - return newlist; -} diff --git a/src/solvers/enumpoly/gtree.h b/src/solvers/enumpoly/gtree.h deleted file mode 100644 index 17eeea245..000000000 --- a/src/solvers/enumpoly/gtree.h +++ /dev/null @@ -1,90 +0,0 @@ -// -// This file is part of Gambit -// Copyright (c) 1994-2024, The Gambit Project (http://www.gambit-project.org) -// -// FILE: src/tools/enumpoly/gtree.h -// A generic tree 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 -// 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 GTREE_H -#define GTREE_H - -#include "gambit.h" - -template class gTree; - -template class gTreeNode { - friend class gTree; - -private: - T data; - gTreeNode *parent, *prev, *next, *eldest, *youngest; - -public: - // Constructor - gTreeNode(const T &_data, gTreeNode *_parent, gTreeNode *_prev, gTreeNode *_next, - gTreeNode *_eldest, gTreeNode *_youngest); - - ~gTreeNode(); - - inline void SetData(const T &newdata) { data = newdata; } - inline void SetParent(gTreeNode *newparent) { parent = newparent; } - inline void SetPrev(gTreeNode *newprev) { prev = newprev; } - inline void SetNext(gTreeNode *newnext) { next = newnext; } - inline void SetEldest(gTreeNode *neweldest) { eldest = neweldest; } - inline void SetYoungest(gTreeNode *newyoungest) { youngest = newyoungest; } - - inline T GetData() const { return data; } - inline gTreeNode *GetParent() const { return parent; } - inline gTreeNode *GetPrev() const { return prev; } - inline gTreeNode *GetNext() const { return next; } - inline gTreeNode *GetEldest() const { return eldest; } - inline gTreeNode *GetYoungest() const { return youngest; } -}; - -template class gTree { -protected: - gTreeNode *root; - - gTreeNode *RecursiveFind(const T &, gTreeNode *) const; - void RecursiveCopy(gTreeNode *, const gTreeNode *); - void RecursiveFlush(const gTreeNode *); - void Flush(); - -public: - gTree(); - explicit gTree(const T &); - gTree(const gTree &); - virtual ~gTree(); - - gTree &operator=(const gTree &); - - bool operator==(const gTree &b) const; - bool operator!=(const gTree &b) const; - - // Constructive Manipulation - void InsertAt(const T &, gTreeNode *); - - // Information - Gambit::List *> Children(const gTreeNode *) const; - gTreeNode *RootNode() const; - gTreeNode *Find(const T &) const; - bool Contains(const T &t) const; - bool SubtreesAreIsomorphic(const gTreeNode *, const gTreeNode *) const; -}; - -#endif // GTREE_H diff --git a/src/solvers/enumpoly/gtree.imp b/src/solvers/enumpoly/gtree.imp deleted file mode 100644 index f57629377..000000000 --- a/src/solvers/enumpoly/gtree.imp +++ /dev/null @@ -1,184 +0,0 @@ -// -// This file is part of Gambit -// Copyright (c) 1994-2024, The Gambit Project (http://www.gambit-project.org) -// -// FILE: src/tools/enumpoly/gtree.imp -// Implementation of a generic tree 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 -// 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 "gtree.h" - -//-------------------------------------------------------------------------- -// gTreeNode: Member function implementations -//-------------------------------------------------------------------------- - -template -gTreeNode::gTreeNode(const T &_data, gTreeNode *_parent, gTreeNode *_prev, - gTreeNode *_next, gTreeNode *_eldest, gTreeNode *_youngest) - : data(_data), parent(_parent), prev(_prev), next(_next), eldest(_eldest), youngest(_youngest) -{ -} - -template gTreeNode::~gTreeNode() = default; - -//-------------------------------------------------------------------------- -// gTree: Member function implementations -//-------------------------------------------------------------------------- - -template gTree::gTree() : root(nullptr) {} - -template gTree::gTree(const T &rootdatum) : root(nullptr) -{ - root = new gTreeNode(rootdatum, nullptr, nullptr, nullptr, nullptr, nullptr); -} - -template gTree::gTree(const gTree &b) : root(nullptr) -{ - if (b.root != nullptr) { - root = new gTreeNode(b.root->data, nullptr, nullptr, nullptr, nullptr, nullptr); - RecursiveCopy(root, b.root); - } -} - -template gTree::~gTree() { Flush(); } - -template void gTree::InsertAt(const T &t, gTreeNode *n) -{ - // assert (n != NULL); - - if (n->eldest == nullptr) { - auto *newn = new gTreeNode(t, n, nullptr, nullptr, nullptr, nullptr); - n->SetEldest(newn); - n->SetYoungest(newn); - } - else { - auto *newn = new gTreeNode(t, n, n->youngest, nullptr, nullptr, nullptr); - n->youngest->SetNext(newn); - n->SetYoungest(newn); - } -} - -template void gTree::RecursiveCopy(gTreeNode *copyn, const gTreeNode *orign) -{ - auto oldchildren = Children(orign); - for (size_t i = 1; i <= oldchildren.size(); i++) { - InsertAt(oldchildren[i]->data, copyn); - } - auto newchildren = Children(copyn); - for (size_t i = 1; i <= newchildren.size(); i++) { - RecursiveCopy(newchildren[i], oldchildren[i]); - } -} - -//--------------------- operators ------------------------ - -template gTree &gTree::operator=(const gTree &b) -{ - if (this != &b) { - Flush(); - if (root != nullptr) { - root = new gTreeNode(b.root->data, nullptr, nullptr, nullptr, nullptr, nullptr); - RecursiveCopy(root, b.root); - } - else { - root = nullptr; - } - } - return *this; -} - -template bool gTree::operator==(const gTree &b) const -{ - if (root == nullptr && b.root == nullptr) { - return true; - } - if (root == nullptr || b.root == nullptr) { - return false; - } - return SubtreesAreIsomorphic(root, b.root); -} - -template bool gTree::operator!=(const gTree &b) const { return !(*this == b); } - -template Gambit::List *> gTree::Children(const gTreeNode *n) const -{ - // assert(n != NULL); - Gambit::List *> answer; - for (gTreeNode *child = n->eldest; child != nullptr; child = child->next) { - answer.push_back(child); - } - return answer; -} - -template gTreeNode *gTree::RootNode() const { return root; } - -template gTreeNode *gTree::RecursiveFind(const T &t, gTreeNode *n) const -{ - gTreeNode *answer = nullptr; - if (n->data == t) { - answer = n; - } - else { - gTreeNode *probe = n->eldest; - while (answer == nullptr && probe != nullptr) { - answer = RecursiveFind(t, probe); - probe = probe->next; - } - } - return answer; -} - -template gTreeNode *gTree::Find(const T &t) const -{ - return RecursiveFind(t, root); -} - -template bool gTree::Contains(const T &t) const { return (Find(t) != nullptr); } - -template -bool gTree::SubtreesAreIsomorphic(const gTreeNode *lhs, const gTreeNode *rhs) const -{ - if (lhs->data != rhs->data) { - return false; - } - auto lchildren = Children(lhs); - auto rchildren = Children(rhs); - if (lchildren.size() != rchildren.size()) { - return false; - } - for (size_t i = 1; i <= lchildren.size(); i++) { - if (!SubtreesAreIsomorphic(lchildren[i], rchildren[i])) { - return false; - } - } - return true; -} - -template void gTree::RecursiveFlush(const gTreeNode *n) -{ - auto children = Children(n); - for (const auto &child : children) { - RecursiveFlush(child); - } - delete n; -} - -template void gTree::Flush() -{ - RecursiveFlush(root); - root = nullptr; -} diff --git a/src/solvers/enumpoly/indexproduct.h b/src/solvers/enumpoly/indexproduct.h new file mode 100644 index 000000000..204c20add --- /dev/null +++ b/src/solvers/enumpoly/indexproduct.h @@ -0,0 +1,89 @@ +// +// This file is part of Gambit +// Copyright (c) 1994-2024, The Gambit Project (http://www.gambit-project.org) +// +// FILE: src/solvers/enumpoly/indexproduct.h +// Utility class representing a product of index ranges +// +// 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 INDEXPRODUCT_H +#define INDEXPRODUCT_H + +#include "gambit.h" + +namespace Gambit { + +// A representation of a cartesian product of indices, with an associated iterator +class CartesianIndexProduct { +private: + Array m_lower, m_upper; + +public: + class iterator { + private: + CartesianIndexProduct *m_set; + Array m_current; + bool m_end; + + public: + iterator(CartesianIndexProduct *p_set, bool p_end) + : m_set(p_set), m_current(p_set->m_lower), m_end(p_end) + { + } + iterator(const iterator &) = default; + ~iterator() = default; + + const Array &operator*() const { return m_current; } + + bool operator==(const iterator &p_other) const + { + if (p_other.m_set != m_set || p_other.m_end != m_end) { + return false; + } + return m_end || m_current == p_other.m_current; + } + bool operator!=(const iterator &p_other) const { return !(*this == p_other); } + iterator &operator++() + { + auto [cur, up] = std::mismatch(m_current.begin(), m_current.end(), m_set->m_upper.begin()); + if (cur == m_current.end()) { + m_end = true; + } + else { + std::copy(m_current.begin(), cur, m_set->m_lower.begin()); + (*cur)++; + } + return *this; + } + }; + + CartesianIndexProduct(const Array &p_lower, const Array &p_upper) + : m_lower(p_lower), m_upper(p_upper) + { + } + CartesianIndexProduct(const CartesianIndexProduct &) = default; + ~CartesianIndexProduct() = default; + + CartesianIndexProduct &operator=(const CartesianIndexProduct &) = default; + + iterator begin() { return iterator(this, false); } + iterator end() { return iterator(this, true); } +}; + +} // namespace Gambit + +#endif // INDEXPRODUCT_H diff --git a/src/solvers/enumpoly/ineqsolv.cc b/src/solvers/enumpoly/ineqsolv.cc deleted file mode 100644 index 1d0c032bb..000000000 --- a/src/solvers/enumpoly/ineqsolv.cc +++ /dev/null @@ -1,26 +0,0 @@ -// -// This file is part of Gambit -// Copyright (c) 1994-2024, The Gambit Project (http://www.gambit-project.org) -// -// FILE: src/tools/enumpoly/ineqsolv.cc -// Instantiation of IneqSolv classes -// -// This program is free software; you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation; either version 2 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -// - -#include "ineqsolv.imp" - -// template class IneqSolv; -template class IneqSolv; diff --git a/src/solvers/enumpoly/ineqsolv.h b/src/solvers/enumpoly/ineqsolv.h deleted file mode 100644 index bf8abba1e..000000000 --- a/src/solvers/enumpoly/ineqsolv.h +++ /dev/null @@ -1,87 +0,0 @@ -// -// This file is part of Gambit -// Copyright (c) 1994-2024, The Gambit Project (http://www.gambit-project.org) -// -// FILE: src/tools/enumpoly/ineqsolv.h -// Declaration of IneqSolv -// -// 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 INEQSOLV_H -#define INEQSOLV_H - -#include "gambit.h" - -#include "odometer.h" -#include "rectangle.h" -#include "gpoly.h" -#include "gpolylst.h" -#include "gpartltr.h" - -/* - The class described in this file is a method of determining whether a -system of weak inequalities has a solution (a point where all are satisfied) -in a given rectangle. Ir is modeled on QuikSolv, but simpler. There is -no Newton search, only repeated subdivision, queries at the center, and -tests against whether one of the inequalities is provably everywhere -negative in the rectangle. -*/ - -/* - The main constructor for this takes a gPolyList, interpreted as -inequalities in the sense that, at a solution, all the polynomials -are required to be nonnegative. -*/ - -// *********************** -// class IneqSolv -// *********************** - -template class IneqSolv { -private: - gPolyList System; - ListOfPartialTrees TreesOfPartials; - T Epsilon; - - // Routines Doing the Actual Work - bool IsASolution(const Gambit::Vector &) const; - - bool SystemHasNoSolutionIn(const Rectangle &r, Gambit::Array &) const; - - bool ASolutionExistsRecursion(const Rectangle &, Gambit::Vector &, - Gambit::Array &) const; - -public: - explicit IneqSolv(const gPolyList &); - IneqSolv(const IneqSolv &); - ~IneqSolv(); - - // Operators - IneqSolv &operator=(const IneqSolv &); - bool operator==(const IneqSolv &) const; - bool operator!=(const IneqSolv &) const; - - // Information - const VariableSpace *AmbientSpace() const { return System.AmbientSpace(); } - int Dmnsn() const { return System.Dmnsn(); } - const gPolyList &UnderlyingEquations() const { return System; } - T ErrorTolerance() const { return Epsilon; } - - // The function that does everything - bool ASolutionExists(const Rectangle &, Gambit::Vector &sample); -}; - -#endif // INEQSOLV_H diff --git a/src/solvers/enumpoly/ineqsolv.imp b/src/solvers/enumpoly/ineqsolv.imp deleted file mode 100644 index 4c269ee42..000000000 --- a/src/solvers/enumpoly/ineqsolv.imp +++ /dev/null @@ -1,176 +0,0 @@ -// -// This file is part of Gambit -// Copyright (c) 1994-2024, The Gambit Project (http://www.gambit-project.org) -// -// FILE: src/tools/enumpoly/ineqsolv.imp -// Implementation of IneqSolv -// -// 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 "ineqsolv.h" - -//--------------------------------------------------------------- -// class: IneqSolv -//--------------------------------------------------------------- - -//--------------------------- -// Constructors / Destructors -//--------------------------- - -template -IneqSolv::IneqSolv(const gPolyList &given) - : System(given), TreesOfPartials(given), Epsilon((T)1 / (T)1000000) -// Epsilon((T)0), -// HasBeenSolved(false), -// HasASolution(triUNKNOWN), -// Sample(given.Dmnsn()), - -{ -} - -template -IneqSolv::IneqSolv(const IneqSolv &qs) - : System(qs.System), TreesOfPartials(qs.TreesOfPartials), Epsilon(qs.Epsilon) -// HasBeenSolved(qs.HasBeenSolved), -// HasASolution(qs.HasASolution), -// Sample(qs.Sample), -{ -} - -template IneqSolv::~IneqSolv() = default; - -//---------------------------------- -// Operators -//---------------------------------- - -template IneqSolv &IneqSolv::operator=(const IneqSolv &rhs) -{ - // assert (System == rhs.System); - - if (*this != rhs) { - Epsilon = rhs.Epsilon; - } - return *this; -} - -template bool IneqSolv::operator==(const IneqSolv &rhs) const -{ - if (System != rhs.System || Epsilon != rhs.Epsilon) { - return false; - } - else { - return true; - } -} - -template bool IneqSolv::operator!=(const IneqSolv &rhs) const -{ - return !(*this == rhs); -} - -//--------------------------- -// Calculations -//--------------------------- - -template bool IneqSolv::IsASolution(const Gambit::Vector &v) const -{ - bool answer(true); - for (int i = 1; i <= System.Length() && answer; i++) { - if (System[i].Evaluate(v) < -Epsilon) { - answer = false; - } - } - return answer; -} - -template -bool IneqSolv::SystemHasNoSolutionIn(const Rectangle &r, - Gambit::Array &precedence) const -{ - for (int i = 1; i <= System.Length(); i++) { - - if (TreesOfPartials[precedence[i]].PolyEverywhereNegativeIn(r)) { - - /* - //DEBUG - gout << "The polynomial " << System[precedence[i]] - << " has no roots in "; - gRectangle newrect(r); - gout << newrect; - gout << ".\n"; - */ - - if (i != 1) { // We have found a new "most likely to never be positive" - int tmp = precedence[i]; - for (int j = 1; j <= i - 1; j++) { - precedence[i - j + 1] = precedence[i - j]; - } - precedence[1] = tmp; - } - return true; - } - } - return false; -} - -template -bool IneqSolv::ASolutionExistsRecursion(const Rectangle &r, Gambit::Vector &sample, - Gambit::Array &precedence) const -{ - /* - //DEBUG - gout << "The rectangle is\n"; - for (int i = 1; i <= r.Dmnsn() - 1; i++) - gout << r.CartesianFactor(i) << "x"; - if (r.Dmnsn() > 0) - gout << r.CartesianFactor(r.Dmnsn()) << "\n"; - */ - - // Check for user interrupt - if (IsASolution(r.Center())) { - return true; - } - - if (SystemHasNoSolutionIn(r, precedence)) { - return false; - } - - for (const auto &cell : r.Orthants()) { - if (ASolutionExistsRecursion(cell, sample, precedence)) { - return true; - } - } - return false; -} - -template -bool IneqSolv::ASolutionExists(const Rectangle &r, Gambit::Vector &sample) -{ - // precedence orders search for everywhere negative poly - Gambit::Array precedence(System.Length()); - for (int i = 1; i <= System.Length(); i++) { - precedence[i] = i; - } - - /* - //DEBUG - gout << "The system is\n" << System << "\n"; - */ - - bool answer = ASolutionExistsRecursion(r, sample, precedence); - - return answer; -} diff --git a/src/solvers/enumpoly/linrcomb.cc b/src/solvers/enumpoly/linrcomb.cc deleted file mode 100644 index ef56e1719..000000000 --- a/src/solvers/enumpoly/linrcomb.cc +++ /dev/null @@ -1,25 +0,0 @@ -// -// This file is part of Gambit -// Copyright (c) 1994-2024, The Gambit Project (http://www.gambit-project.org) -// -// FILE: src/tools/enumpoly/linrcomb.cc -// Instantiation of linear combination 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 "linrcomb.imp" - -template class LinearCombination; diff --git a/src/solvers/enumpoly/linrcomb.h b/src/solvers/enumpoly/linrcomb.h deleted file mode 100644 index fcf5a5095..000000000 --- a/src/solvers/enumpoly/linrcomb.h +++ /dev/null @@ -1,68 +0,0 @@ -// -// This file is part of Gambit -// Copyright (c) 1994-2024, The Gambit Project (http://www.gambit-project.org) -// -// FILE: src/tools/enumpoly/linrcomb.h -// Find linear weights for dependencies between rows of a matrix -// -// 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. -// - -/* - This file contains the file for a class that performs a very specific -computation, namely asking whether the last row of a matrix is a linear -combination of the other rows, and if so computing the coefficients of -a linear dependence. - All computation is done in the constructor. The constructor should -only be called when there is a guarantee (which this class may be used -to compute!) that the rows other than the last are linearly -independent. -*/ - -#ifndef LINRCOMB_H -#define LINRCOMB_H - -#include "core/rational.h" -#include "core/matrix.h" - -template class LinearCombination { -private: - Gambit::Matrix scrambled; - Gambit::Vector weights; - bool last_row_is_spanned; - - void AddMultipleOfRowiToRowj(const int &i, const int &j, const T &scalar); - void AddMultipleOfRowiToRowj(const int &i, const int &j, const T &scalar, Gambit::Matrix &B); - - // This function is left unimplemented to avoid copying - LinearCombination &operator=(const LinearCombination &); - -public: - // Constructors, Destructor, Constructive Operators - explicit LinearCombination(const Gambit::Matrix &); - LinearCombination(const LinearCombination &); - - virtual ~LinearCombination(); - - // Comparison Operators - bool operator==(const LinearCombination &) const; - bool operator!=(const LinearCombination &) const; - - // Information - bool LastRowIsSpanned() const; - Gambit::Vector LinearDependence() const; -}; - -#endif // LinearCombination_H diff --git a/src/solvers/enumpoly/linrcomb.imp b/src/solvers/enumpoly/linrcomb.imp deleted file mode 100644 index a09934fe4..000000000 --- a/src/solvers/enumpoly/linrcomb.imp +++ /dev/null @@ -1,200 +0,0 @@ -// -// This file is part of Gambit -// Copyright (c) 1994-2024, The Gambit Project (http://www.gambit-project.org) -// -// FILE: src/tools/enumpoly/linrcomb.imp -// Implementation of linear combination 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 -#include "gambit.h" -#include "linrcomb.h" - -//------------------------------------------------------------------------- -// LinearCombination: Constructors, destructors, constructive operators -//------------------------------------------------------------------------- - -template -LinearCombination::LinearCombination(const Gambit::Matrix &M) - : scrambled(M), weights(M.NumRows()), last_row_is_spanned(true) -{ - int K = scrambled.NumRows() - 1; - - int i, j; - Gambit::Matrix B(K, K); // Initialize the matrix recording the - for (i = 1; i <= K; i++) { // effect of all row operations. - for (j = 1; j <= K; j++) { - if (i == j) { - B(i, j) = 1; - } - else { - B(i, j) = 0; - } - } - } - - Gambit::Vector PivotRow(K); - for (i = 1; i <= K; i++) { - PivotRow[i] = i; - } - Gambit::Vector PivotColumn(K); - - int c = 1; - for (i = 1; i <= K - 1; i++) { // Each iteration is a - // Gaussian column clearing - - int i1 = i; // Find pivot row and column - while (scrambled(PivotRow[i1], c) == (T)0 && c <= M.NumColumns()) { - if (i1 < K) { - i1++; - } - else { - c++; - i1 = i; - } - } - if (c > M.NumColumns()) { - // gout << "First NumRows() rows not linearly independent in " - // << "LinearCombination(Gambit::Matrix M).\n"; - exit(1); - } - - int tmp = PivotRow[i]; // Swap - PivotRow[i] = PivotRow[i1]; - PivotRow[i1] = tmp; - - PivotColumn[i] = c; - - for (int i2 = 1; i2 <= K; i2++) { // Each i2 is a pair of row operations. - if (i2 != i) { - T factor = - scrambled(PivotRow[i2], PivotColumn[i]) / scrambled(PivotRow[i], PivotColumn[i]); - AddMultipleOfRowiToRowj(PivotRow[i], PivotRow[i2], -factor, scrambled); - AddMultipleOfRowiToRowj(PivotRow[i], PivotRow[i2], -factor, B); - } - } - - c++; - } - - if (K > 0) { - if (K == 1) { - PivotColumn[K] = 1; - } - else { - PivotColumn[K] = PivotColumn[K - 1] + 1; - } - while (PivotColumn[K] <= M.NumColumns() && scrambled(PivotRow[K], PivotColumn[K]) == (T)0) { - PivotColumn[K]++; - } - if (PivotColumn[K] > M.NumColumns()) { - // gout << "First NumRows()-1 rows not linearly independent in " - // << "LinearCombination(Gambit::Matrix M).\n"; - // gout << "At the time of failure, scrambled is \n" - // << scrambled << "\n"; - exit(1); - } - - for (int i2 = 1; i2 < K; i2++) { // Each i2 is a pair of row operations. - T factor = scrambled(PivotRow[i2], PivotColumn[K]) / scrambled(PivotRow[K], PivotColumn[K]); - AddMultipleOfRowiToRowj(PivotRow[K], PivotRow[i2], -factor); - AddMultipleOfRowiToRowj(PivotRow[K], PivotRow[i2], -factor, B); - } - } - - Gambit::Vector xBinverse(K); - for (int i3 = 1; i3 <= K; i3++) { // Each i3 is a row operation on last row. - T factor = scrambled(K + 1, PivotColumn[i3]) / scrambled(PivotRow[i3], PivotColumn[i3]); - xBinverse[PivotRow[i3]] = factor; - AddMultipleOfRowiToRowj(PivotRow[i3], K + 1, -factor); - } - Gambit::Vector x(xBinverse * B); - - for (j = 1; j <= M.NumColumns(); j++) { - if (scrambled(K + 1, j) != (T)0) { - last_row_is_spanned = false; - } - } - - if (last_row_is_spanned) { - for (int i4 = 1; i4 <= K; i4++) { - weights[i4] = -x[i4]; - } - weights[K + 1] = 1; - } -} - -template -LinearCombination::LinearCombination(const LinearCombination &L) - : scrambled(L.scrambled), weights(L.weights), last_row_is_spanned(L.last_row_is_spanned) -{ -} - -template LinearCombination::~LinearCombination() = default; - -//------------------------------------------------------------------------- -// LinearCombination: Comparison operators -//------------------------------------------------------------------------- - -template bool LinearCombination::operator==(const LinearCombination &L) const -{ - if (scrambled == L.scrambled && last_row_is_spanned == L.last_row_is_spanned && - weights == L.weights) { - return true; - } - else { - return false; - } -} - -template bool LinearCombination::operator!=(const LinearCombination &L) const -{ - return !(*this == L); -} - -//------------------------------------------------------------------------- -// LinearCombination: Information -//------------------------------------------------------------------------- - -template bool LinearCombination::LastRowIsSpanned() const -{ - return last_row_is_spanned; -} - -template Gambit::Vector LinearCombination::LinearDependence() const -{ - return weights; -} - -//------------------------------------------------------------------------- -// LinearCombination: Private Members -//------------------------------------------------------------------------- - -template -void LinearCombination::AddMultipleOfRowiToRowj(const int &i, const int &j, const T &scalar, - Gambit::Matrix &B) -{ - for (int k = 1; k <= B.NumColumns(); k++) { - B(j, k) += scalar * B(i, k); - } -} - -template -void LinearCombination::AddMultipleOfRowiToRowj(const int &i, const int &j, const T &scalar) -{ - AddMultipleOfRowiToRowj(i, j, scalar, scrambled); -} diff --git a/src/solvers/enumpoly/ndarray.h b/src/solvers/enumpoly/ndarray.h index 801e5b43c..b3181cc14 100644 --- a/src/solvers/enumpoly/ndarray.h +++ b/src/solvers/enumpoly/ndarray.h @@ -2,8 +2,8 @@ // This file is part of Gambit // Copyright (c) 1994-2024, The Gambit Project (http://www.gambit-project.org) // -// FILE: src/tools/enumpoly/gnarray.h -// Interface declaration for N-dimensional arrays +// FILE: src/solvers/enumpoly/ndarray.h +// A simple N-dimensional array 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 diff --git a/src/solvers/enumpoly/nfgpoly.cc b/src/solvers/enumpoly/nfgpoly.cc index 58ae0f165..795c547e0 100644 --- a/src/solvers/enumpoly/nfgpoly.cc +++ b/src/solvers/enumpoly/nfgpoly.cc @@ -2,7 +2,7 @@ // This file is part of Gambit // Copyright (c) 1994-2024, The Gambit Project (http://www.gambit-project.org) // -// FILE: src/tools/enumpoly/nfgpoly.cc +// FILE: src/solvers/enumpoly/nfgpoly.cc // Enumerates all Nash equilibria in a normal form game, via solving // systems of polynomial equations // @@ -21,15 +21,12 @@ // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // -#include #include #include "enumpoly.h" #include "solvers/nashsupport/nashsupport.h" -#include "gpoly.h" -#include "gpolylst.h" -#include "rectangle.h" -#include "quiksolv.h" +#include "polysystem.h" +#include "polysolver.h" using namespace Gambit; @@ -37,17 +34,17 @@ namespace { // The polynomial representation of each strategy probability, substituting in // the sum-to-one equation for the probability of the last strategy for each player -std::map> BuildStrategyVariables(const VariableSpace &space, - const StrategySupportProfile &support) +std::map> +BuildStrategyVariables(std::shared_ptr space, const StrategySupportProfile &support) { int index = 1; - std::map> strategy_poly; + std::map> strategy_poly; for (auto player : support.GetGame()->GetPlayers()) { auto strategies = support.GetStrategies(player); - gPoly residual(&space, 1); + Polynomial residual(space, 1); for (auto strategy : strategies) { if (strategy != strategies.back()) { - strategy_poly.try_emplace(strategy, &space, index, 1); + strategy_poly.try_emplace(strategy, space, index, 1); residual -= strategy_poly.at(strategy); index++; } @@ -59,15 +56,15 @@ std::map> BuildStrategyVariables(const VariableSpace return strategy_poly; } -gPoly IndifferenceEquation(const VariableSpace &space, - const StrategySupportProfile &support, - const std::map> &strategy_poly, - const GameStrategy &s1, const GameStrategy &s2) +Polynomial +IndifferenceEquation(std::shared_ptr space, const StrategySupportProfile &support, + const std::map> &strategy_poly, + const GameStrategy &s1, const GameStrategy &s2) { - gPoly equation(&space); + Polynomial equation(space); for (auto iter : StrategyContingencies(support, {s1})) { - gPoly term(&space, 1); + Polynomial term(space, 1); for (auto player : support.GetGame()->GetPlayers()) { if (player != s1->GetPlayer()) { term *= strategy_poly.at(iter->GetStrategy(player)); @@ -79,22 +76,22 @@ gPoly IndifferenceEquation(const VariableSpace &space, return equation; } -gPolyList ConstructEquations(const VariableSpace &space, - const StrategySupportProfile &support, - const std::map> &strategy_poly) +PolynomialSystem +ConstructEquations(std::shared_ptr space, const StrategySupportProfile &support, + const std::map> &strategy_poly) { - gPolyList equations(&space); + PolynomialSystem equations(space); // Indifference equations between pairs of strategies for each player for (auto player : support.GetPlayers()) { auto strategies = support.GetStrategies(player); for (auto s1 = strategies.begin(), s2 = std::next(strategies.begin()); s2 != strategies.end(); ++s1, ++s2) { - equations += IndifferenceEquation(space, support, strategy_poly, *s1, *s2); + equations.push_back(IndifferenceEquation(space, support, strategy_poly, *s1, *s2)); } } // Inequalities for last probability for each player for (auto player : support.GetPlayers()) { - equations += strategy_poly.at(support.GetStrategies(player).back()); + equations.push_back(strategy_poly.at(support.GetStrategies(player).back())); } return equations; } @@ -108,18 +105,19 @@ std::list> EnumPolyStrategySupportSolve(const StrategySupportProfile &support, bool &is_singular, int p_stopAfter) { - VariableSpace Space(support.MixedProfileLength() - support.GetGame()->NumPlayers()); + auto space = std::make_shared(support.MixedProfileLength() - + support.GetGame()->NumPlayers()); - auto strategy_poly = BuildStrategyVariables(Space, support); - gPolyList equations = ConstructEquations(Space, support, strategy_poly); + auto strategy_poly = BuildStrategyVariables(space, support); + PolynomialSystem equations = ConstructEquations(space, support, strategy_poly); - Vector bottoms(Space.Dmnsn()), tops(Space.Dmnsn()); + Vector bottoms(space->GetDimension()), tops(space->GetDimension()); bottoms = 0; tops = 1; - QuikSolv solver(equations); + PolynomialSystemSolver solver(equations); is_singular = false; try { - solver.FindCertainNumberOfRoots({bottoms, tops}, std::numeric_limits::max(), p_stopAfter); + solver.FindRoots({bottoms, tops}, p_stopAfter); } catch (const SingularMatrixException &) { is_singular = true; diff --git a/src/solvers/enumpoly/odometer.cc b/src/solvers/enumpoly/odometer.cc deleted file mode 100644 index fadb7cd23..000000000 --- a/src/solvers/enumpoly/odometer.cc +++ /dev/null @@ -1,82 +0,0 @@ -// -// This file is part of Gambit -// Copyright (c) 1994-2024, The Gambit Project (http://www.gambit-project.org) -// -// FILE: src/tools/enumpoly/odometer.cc -// Implementation of class gIndexOdometer -// -// 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 -#include "odometer.h" - -//--------------------------------------------------------------- -// gIndexOdometer -//--------------------------------------------------------------- - -//--------------------------- -// Constructors / Destructors -//--------------------------- - -gIndexOdometer::gIndexOdometer(const Gambit::Array &IndexUpperBounds) - : MinIndices(IndexUpperBounds.size()), MaxIndices(IndexUpperBounds), - CurIndices(IndexUpperBounds.size()) -{ - int i; - for (i = 1; i <= NoIndices(); i++) { - MinIndices[i] = 1; - } - CurIndices[1] = 0; - for (i = 2; i <= NoIndices(); i++) { - CurIndices[i] = 1; - } -} - -gIndexOdometer::gIndexOdometer(const Gambit::Array &IndexLowerBounds, - const Gambit::Array &IndexUpperBounds) - : MinIndices(IndexLowerBounds), MaxIndices(IndexUpperBounds), CurIndices(IndexUpperBounds.size()) -{ - CurIndices[1] = MinIndices[1] - 1; - for (int i = 2; i <= NoIndices(); i++) { - CurIndices[i] = MinIndices[i]; - } -} - -//---------------------------------- -// Manipulate -//---------------------------------- - -bool gIndexOdometer::Turn() -{ - if (CurIndices[1] == MinIndices[1] - 1) { - CurIndices[1] = MinIndices[1]; - return true; - } - - int turn_index = 1; - while (turn_index <= NoIndices() && CurIndices[turn_index] == MaxIndices[turn_index]) { - turn_index++; - } - if (turn_index > NoIndices()) { - return false; - } - - for (int j = 1; j < turn_index; j++) { - CurIndices[j] = MinIndices[j]; - } - CurIndices[turn_index]++; - return true; -} diff --git a/src/solvers/enumpoly/odometer.h b/src/solvers/enumpoly/odometer.h deleted file mode 100644 index e6554f156..000000000 --- a/src/solvers/enumpoly/odometer.h +++ /dev/null @@ -1,75 +0,0 @@ -// -// This file is part of Gambit -// Copyright (c) 1994-2024, The Gambit Project (http://www.gambit-project.org) -// -// FILE: src/tools/enumpoly/odometer.h -// Declaration of gIndexOdometer 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. -// - -/* - When the cartesian product of ordered sets is ordered -lexicographically, there is a relation between such a relation, -and the numerical indexing derived from the lexicographical -ordering, that is similar to an odometer. Here the least -significant index is the first ("leftmost"). -*/ - -#ifndef ODOMETER_H -#define ODOMETER_H - -#include "gambit.h" - -// ***************************** -// class gIndexOdometer -// ***************************** - -class gIndexOdometer { -private: - Gambit::Array MinIndices; - Gambit::Array MaxIndices; - Gambit::Array CurIndices; - -public: - explicit gIndexOdometer(const Gambit::Array &); - gIndexOdometer(const Gambit::Array &, const Gambit::Array &); - gIndexOdometer(const gIndexOdometer &) = default; - ~gIndexOdometer() = default; - - // Operators - gIndexOdometer &operator=(const gIndexOdometer &) = default; - - bool operator==(const gIndexOdometer &p_rhs) const - { - return (MinIndices == p_rhs.MinIndices) && (MaxIndices == p_rhs.MaxIndices) && - (CurIndices == p_rhs.CurIndices); - } - bool operator!=(const gIndexOdometer &p_rhs) const - { - return (MinIndices != p_rhs.MinIndices) || (MaxIndices != p_rhs.MaxIndices) || - (CurIndices != p_rhs.CurIndices); - } - - int operator[](int place) const { return CurIndices[place]; } - - // Manipulate - bool Turn(); - - // Information - int NoIndices() const { return MaxIndices.size(); } -}; - -#endif // ODOMETER_H diff --git a/src/solvers/enumpoly/gpoly.cc b/src/solvers/enumpoly/poly.cc similarity index 73% rename from src/solvers/enumpoly/gpoly.cc rename to src/solvers/enumpoly/poly.cc index 2cfe96f35..4e426181d 100644 --- a/src/solvers/enumpoly/gpoly.cc +++ b/src/solvers/enumpoly/poly.cc @@ -2,8 +2,8 @@ // This file is part of Gambit // Copyright (c) 1994-2024, The Gambit Project (http://www.gambit-project.org) // -// FILE: src/tools/enumpoly/gpoly.cc -// Instantiation of common gPoly classes +// FILE: src/solvers/enumpoly/poly.cc +// Instantiation of polynomial classes // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -20,7 +20,13 @@ // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // -#include "gpoly.imp" -#include "gambit.h" +#include "poly.imp" +#include "polypartial.imp" -template class gPoly; +namespace Gambit { + +template class Polynomial; +template class PolynomialDerivatives; +template class PolynomialSystemDerivatives; + +} // end namespace Gambit diff --git a/src/solvers/enumpoly/poly.h b/src/solvers/enumpoly/poly.h new file mode 100644 index 000000000..593010329 --- /dev/null +++ b/src/solvers/enumpoly/poly.h @@ -0,0 +1,339 @@ +// +// This file is part of Gambit +// Copyright (c) 1994-2024, The Gambit Project (http://www.gambit-project.org) +// +// FILE: src/solvers/enumpoly/poly.h +// Declaration of multivariate polynomial type +// +// 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 POLY_H +#define POLY_H + +#include "gambit.h" +#include "core/sqmatrix.h" + +namespace Gambit { + +class VariableSpace { +public: + struct Variable { + std::string name; + int number; + }; + + explicit VariableSpace(size_t nvars) + { + for (size_t i = 1; i <= nvars; i++) { + m_variables.push_back({"n" + std::to_string(i), static_cast(i)}); + } + } + VariableSpace(const VariableSpace &) = delete; + ~VariableSpace() = default; + VariableSpace &operator=(const VariableSpace &) = delete; + const Variable &operator[](const int index) const { return m_variables[index]; } + int GetDimension() const { return m_variables.size(); } + +private: + Array m_variables; +}; + +// An exponent vector is a vector of integers representing the exponents on variables in +// a space in a monomial. +// +// Exponent vectors are ordered lexicographically. +class ExponentVector { +private: + std::shared_ptr m_space; + Vector m_components; + +public: + explicit ExponentVector(std::shared_ptr p) + : m_space(p), m_components(p->GetDimension()) + { + m_components = 0; + } + // Construct x_i^j + ExponentVector(std::shared_ptr p, const int i, const int j) + : m_space(p), m_components(p->GetDimension()) + { + m_components = 0; + m_components[i] = j; + } + ExponentVector(const ExponentVector &) = default; + ~ExponentVector() = default; + ExponentVector &operator=(const ExponentVector &) = default; + + std::shared_ptr GetSpace() const { return m_space; } + int operator[](int index) const { return m_components[index]; } + + bool operator==(const ExponentVector &y) const + { + return m_space == y.m_space && m_components == y.m_components; + } + bool operator!=(const ExponentVector &y) const + { + return m_space != y.m_space || m_components != y.m_components; + } + ExponentVector operator+(const ExponentVector &v) const + { + ExponentVector tmp(*this); + tmp.m_components += v.m_components; + return tmp; + } + + ExponentVector WithZeroExponent(const int varnumber) const + { + ExponentVector tmp(*this); + tmp.m_components[varnumber] = 0; + return tmp; + } + + int GetDimension() const { return m_space->GetDimension(); } + bool IsMultiaffine() const + { + return std::all_of(m_components.begin(), m_components.end(), [](int x) { return x <= 1; }); + } + int TotalDegree() const { return std::accumulate(m_components.begin(), m_components.end(), 0); } + + void ToZero() { m_components = 0; } + + bool operator<(const ExponentVector &y) const + { + for (int i = 1; i <= GetDimension(); i++) { + if (m_components[i] < y.m_components[i]) { + return true; + } + if (m_components[i] > y.m_components[i]) { + return false; + } + } + return false; + } + bool operator<=(const ExponentVector &y) const { return *this < y || *this == y; } + bool operator>(const ExponentVector &y) const { return !(*this <= y); } + bool operator>=(const ExponentVector &y) const { return !(*this < y); } +}; + +/// A monomial of multiple variables with non-negative exponents +template class Monomial { +private: + T coef; + ExponentVector exps; + +public: + Monomial(std::shared_ptr p, const T &x) : coef(x), exps(p) {} + Monomial(const T &x, const ExponentVector &e) : coef(x), exps(e) + { + if (x == static_cast(0)) { + exps.ToZero(); + } + } + Monomial(const Monomial &) = default; + ~Monomial() = default; + + // operators + Monomial &operator=(const Monomial &) = default; + + bool operator==(const Monomial &y) const { return (coef == y.coef && exps == y.exps); } + bool operator!=(const Monomial &y) const { return (coef != y.coef || exps != y.exps); } + Monomial operator*(const Monomial &y) const { return {coef * y.coef, exps + y.exps}; } + Monomial operator+(const Monomial &y) const { return {coef + y.coef, exps}; } + Monomial &operator+=(const Monomial &y) + { + coef += y.coef; + return *this; + } + Monomial &operator*=(const T &v) + { + coef *= v; + return *this; + } + Monomial operator-() const { return {-coef, exps}; } + + std::shared_ptr GetSpace() const { return exps.GetSpace(); } + const T &Coef() const { return coef; } + int TotalDegree() const { return exps.TotalDegree(); } + bool IsMultiaffine() const { return exps.IsMultiaffine(); } + const ExponentVector &ExpV() const { return exps; } + T Evaluate(const Vector &vals) const + { + T answer = coef; + for (int i = 1; i <= exps.GetDimension(); i++) { + for (int j = 1; j <= exps[i]; j++) { + answer *= vals[i]; + } + } + return answer; + } +}; + +// A multivariate polynomial +template class Polynomial { +private: + std::shared_ptr m_space; + List> m_terms; + + // Arithmetic + List> Adder(const List> &, const List> &) const; + List> Mult(const List> &, const List> &) const; + Polynomial DivideByPolynomial(const Polynomial &den) const; + + Polynomial TranslateOfMono(const Monomial &, const Vector &) const; + Polynomial MonoInNewCoordinates(const Monomial &, const SquareMatrix &) const; + +public: + Polynomial(std::shared_ptr p) : m_space(p) {} + // A constant polynomial + Polynomial(std::shared_ptr p, const T &constant) : m_space(p) + { + if (constant != static_cast(0)) { + m_terms.push_back(Monomial(p, constant)); + } + } + Polynomial(const Polynomial &) = default; + // Constructs a polynomial x_{var_no}^exp + Polynomial(std::shared_ptr p, const int var_no, const int exp) : m_space(p) + { + m_terms.push_back(Monomial(static_cast(1), ExponentVector(p, var_no, exp))); + } + // Constructs a polynomial with single monomial + explicit Polynomial(const Monomial &mono) : m_space(mono.GetSpace()) + { + m_terms.push_back(mono); + } + ~Polynomial() = default; + + Polynomial &operator=(const Polynomial &) = default; + Polynomial operator-() const + { + Polynomial neg(*this); + for (size_t j = 1; j <= m_terms.size(); j++) { + neg.m_terms[j] = -m_terms[j]; + } + return neg; + } + Polynomial operator-(const Polynomial &p) const + { + Polynomial dif(*this); + dif -= p; + return dif; + } + void operator-=(const Polynomial &p) + { + Polynomial neg = p; + for (size_t i = 1; i <= neg.m_terms.size(); i++) { + neg.m_terms[i] = -neg.m_terms[i]; + } + m_terms = Adder(m_terms, neg.m_terms); + } + Polynomial operator+(const Polynomial &p) const + { + Polynomial sum(*this); + sum += p; + return sum; + } + Polynomial operator+(const T &v) const + { + Polynomial result(*this); + result += v; + return result; + } + void operator+=(const Polynomial &p) { m_terms = Adder(m_terms, p.m_terms); } + void operator+=(const T &val) { *this += Polynomial(m_space, val); } + Polynomial operator*(const Polynomial &p) const + { + Polynomial prod(*this); + prod *= p; + return prod; + } + Polynomial operator*(const T &v) const + { + Polynomial result(*this); + result *= v; + return result; + } + void operator*=(const Polynomial &p) { m_terms = Mult(m_terms, p.m_terms); } + void operator*=(const T &val) + { + for (size_t j = 1; j <= m_terms.size(); j++) { + m_terms[j] *= val; + } + } + Polynomial operator/(const T &val) const + { + if (val == static_cast(0)) { + throw ZeroDivideException(); + } + return (*this) * (static_cast(1) / val); + } + Polynomial operator/(const Polynomial &den) const { return DivideByPolynomial(den); } + + bool operator==(const Polynomial &p) const + { + return m_space == p.m_space && m_terms == p.m_terms; + } + bool operator!=(const Polynomial &p) const + { + return m_space != p.m_space || m_terms != p.m_terms; + } + + //------------- + // Information + //------------- + + std::shared_ptr GetSpace() const { return m_space; } + int GetDimension() const { return m_space->GetDimension(); } + int DegreeOfVar(int var_no) const + { + return std::accumulate( + m_terms.begin(), m_terms.end(), 0, + [&var_no](int v, const Monomial &m) { return std::max(v, m.ExpV()[var_no]); }); + } + int Degree() const + { + return std::accumulate(m_terms.begin(), m_terms.end(), 0, [](int v, const Monomial &m) { + return std::max(v, m.TotalDegree()); + }); + } + Polynomial LeadingCoefficient(int varnumber) const; + T NumLeadCoeff() const + { + return (m_terms.size() == 1) ? m_terms.front().Coef() : static_cast(0); + } + bool IsMultiaffine() const + { + return std::all_of(m_terms.begin(), m_terms.end(), + [](const Monomial &t) { return t.IsMultiaffine(); }); + } + T Evaluate(const Vector &values) const + { + return std::accumulate( + m_terms.begin(), m_terms.end(), static_cast(0), + [&values](const T &v, const Monomial &m) { return v + m.Evaluate(values); }); + } + Polynomial PartialDerivative(int varnumber) const; + const List> &MonomialList() const { return m_terms; } + + Polynomial TranslateOfPoly(const Vector &) const; + Polynomial PolyInNewCoordinates(const SquareMatrix &) const; + T MaximalValueOfNonlinearPart(const T &) const; + Polynomial Normalize() const; +}; + +} // end namespace Gambit + +#endif // POLY_H diff --git a/src/solvers/enumpoly/gpoly.imp b/src/solvers/enumpoly/poly.imp similarity index 57% rename from src/solvers/enumpoly/gpoly.imp rename to src/solvers/enumpoly/poly.imp index 87420f7d9..6aa37595b 100644 --- a/src/solvers/enumpoly/gpoly.imp +++ b/src/solvers/enumpoly/poly.imp @@ -2,7 +2,7 @@ // This file is part of Gambit // Copyright (c) 1994-2024, The Gambit Project (http://www.gambit-project.org) // -// FILE: src/tools/enumpoly/gpoly.imp +// FILE: src/solvers/enumpoly/poly.imp // Implementation of multivariate polynomial type // // This program is free software; you can redistribute it and/or modify @@ -21,19 +21,17 @@ // #include -#include "gpoly.h" -#include "gambit.h" +#include "poly.h" -//--------------------------------------------------------------- -// gPoly -//--------------------------------------------------------------- +namespace Gambit { //------------------------------------------------------------- // Private Versions of Arithmetic Operators //------------------------------------------------------------- template -List> gPoly::Adder(const List> &One, const List> &Two) const +List> Polynomial::Adder(const List> &One, + const List> &Two) const { if (One.empty()) { return Two; @@ -42,7 +40,7 @@ List> gPoly::Adder(const List> &One, const List> & return One; } - List> answer; + List> answer; size_t i = 1; size_t j = 1; @@ -78,9 +76,10 @@ List> gPoly::Adder(const List> &One, const List> & } template -List> gPoly::Mult(const List> &One, const List> &Two) const +List> Polynomial::Mult(const List> &One, + const List> &Two) const { - List> answer; + List> answer; if (One.empty() || Two.empty()) { return answer; @@ -88,7 +87,7 @@ List> gPoly::Mult(const List> &One, const List> &T for (size_t i = 1; i <= One.size(); i++) { for (size_t j = 1; j <= Two.size(); j++) { - gMono next = One[i] * Two[j]; + Monomial next = One[i] * Two[j]; if (answer.empty()) { answer.push_back(next); @@ -137,16 +136,16 @@ List> gPoly::Mult(const List> &One, const List> &T return answer; } -template gPoly gPoly::DivideByPolynomial(const gPoly &den) const +template Polynomial Polynomial::DivideByPolynomial(const Polynomial &den) const { - gPoly zero(Space, static_cast(0)); + Polynomial zero(m_space, static_cast(0)); if (den == zero) { throw ZeroDivideException(); } // assumes exact divisibility! - gPoly result = zero; + Polynomial result = zero; if (*this == zero) { return result; @@ -156,16 +155,17 @@ template gPoly gPoly::DivideByPolynomial(const gPoly &den) co return result; } else { - int last = Dmnsn(); + int last = GetDimension(); while (den.DegreeOfVar(last) == 0) { last--; } - gPoly remainder = *this; + Polynomial remainder = *this; while (remainder != zero) { - gPoly quot = remainder.LeadingCoefficient(last) / den.LeadingCoefficient(last); - gPoly power_of_last(Space, last, remainder.DegreeOfVar(last) - den.DegreeOfVar(last)); + Polynomial quot = remainder.LeadingCoefficient(last) / den.LeadingCoefficient(last); + Polynomial power_of_last(m_space, last, + remainder.DegreeOfVar(last) - den.DegreeOfVar(last)); result += quot * power_of_last; remainder -= quot * power_of_last * den; } @@ -173,19 +173,20 @@ template gPoly gPoly::DivideByPolynomial(const gPoly &den) co return result; } -template gPoly gPoly::PartialDerivative(int varnumber) const +template Polynomial Polynomial::PartialDerivative(int varnumber) const { - gPoly newPoly(*this); + Polynomial newPoly(*this); - for (size_t i = 1; i <= newPoly.Terms.size(); i++) { - newPoly.Terms[i] = gMono(newPoly.Terms[i].Coef() * (T)newPoly.Terms[i].ExpV()[varnumber], - newPoly.Terms[i].ExpV().WithZeroExponent(varnumber)); + for (size_t i = 1; i <= newPoly.m_terms.size(); i++) { + newPoly.m_terms[i] = + Monomial(newPoly.m_terms[i].Coef() * (T)newPoly.m_terms[i].ExpV()[varnumber], + newPoly.m_terms[i].ExpV().WithZeroExponent(varnumber)); } size_t j = 1; - while (j <= newPoly.Terms.size()) { - if (newPoly.Terms[j].Coef() == static_cast(0)) { - newPoly.Terms.erase(std::next(newPoly.Terms.begin(), j - 1)); + while (j <= newPoly.m_terms.size()) { + if (newPoly.m_terms[j].Coef() == static_cast(0)) { + newPoly.m_terms.erase(std::next(newPoly.m_terms.begin(), j - 1)); } else { j++; @@ -195,28 +196,29 @@ template gPoly gPoly::PartialDerivative(int varnumber) const return newPoly; } -template gPoly gPoly::LeadingCoefficient(int varnumber) const +template Polynomial Polynomial::LeadingCoefficient(int varnumber) const { - gPoly newPoly(*this); + Polynomial newPoly(*this); int degree = DegreeOfVar(varnumber); - newPoly.Terms = List>(); - for (size_t j = 1; j <= Terms.size(); j++) { - if (Terms[j].ExpV()[varnumber] == degree) { - newPoly.Terms.push_back( - gMono(Terms[j].Coef(), Terms[j].ExpV().WithZeroExponent(varnumber))); + newPoly.m_terms = List>(); + for (size_t j = 1; j <= m_terms.size(); j++) { + if (m_terms[j].ExpV()[varnumber] == degree) { + newPoly.m_terms.push_back( + Monomial(m_terms[j].Coef(), m_terms[j].ExpV().WithZeroExponent(varnumber))); } } return newPoly; } template -gPoly gPoly::TranslateOfMono(const gMono &m, const Vector &new_origin) const +Polynomial Polynomial::TranslateOfMono(const Monomial &m, + const Vector &new_origin) const { - gPoly answer(GetSpace(), static_cast(1)); - for (int i = 1; i <= Dmnsn(); i++) { + Polynomial answer(GetSpace(), static_cast(1)); + for (int i = 1; i <= GetDimension(); i++) { if (m.ExpV()[i] > 0) { - gPoly lt(GetSpace(), i, 1); - lt += gPoly(GetSpace(), new_origin[i]); + Polynomial lt(GetSpace(), i, 1); + lt += Polynomial(GetSpace(), new_origin[i]); for (int j = 1; j <= m.ExpV()[i]; j++) { answer *= lt; } @@ -226,9 +228,9 @@ gPoly gPoly::TranslateOfMono(const gMono &m, const Vector &new_origi return answer; } -template gPoly gPoly::TranslateOfPoly(const Vector &new_origin) const +template Polynomial Polynomial::TranslateOfPoly(const Vector &new_origin) const { - gPoly answer(GetSpace()); + Polynomial answer(m_space); for (size_t i = 1; i <= this->MonomialList().size(); i++) { answer += TranslateOfMono(this->MonomialList()[i], new_origin); } @@ -236,16 +238,16 @@ template gPoly gPoly::TranslateOfPoly(const Vector &new_origi } template -gPoly gPoly::MonoInNewCoordinates(const gMono &m, const SquareMatrix &M) const +Polynomial Polynomial::MonoInNewCoordinates(const Monomial &m, + const SquareMatrix &M) const { - gPoly answer(Space, static_cast(1)); + Polynomial answer(m_space, static_cast(1)); - for (int i = 1; i <= Dmnsn(); i++) { + for (int i = 1; i <= GetDimension(); i++) { if (m.ExpV()[i] > 0) { - gPoly linearform(Space, static_cast(0)); - for (int j = 1; j <= Dmnsn(); j++) { - ExponentVector exps(GetSpace(), j, 1); - linearform += gPoly(GetSpace(), exps, M(i, j)); + Polynomial linearform(m_space, static_cast(0)); + for (int j = 1; j <= GetDimension(); j++) { + linearform += Polynomial(Monomial(M(i, j), ExponentVector(GetSpace(), j, 1))); } for (int k = 1; k <= m.ExpV()[i]; k++) { answer *= linearform; @@ -256,33 +258,36 @@ gPoly gPoly::MonoInNewCoordinates(const gMono &m, const SquareMatrix return answer; } -template gPoly gPoly::PolyInNewCoordinates(const SquareMatrix &M) const +template +Polynomial Polynomial::PolyInNewCoordinates(const SquareMatrix &M) const { - gPoly answer(Space); - for (const auto &term : Terms) { + Polynomial answer(m_space); + for (const auto &term : m_terms) { answer += MonoInNewCoordinates(term, M); } return answer; } -template T gPoly::MaximalValueOfNonlinearPart(const T &radius) const +template T Polynomial::MaximalValueOfNonlinearPart(const T &radius) const { T maxcon = static_cast(0); - for (const auto &term : Terms) { + for (const auto &term : m_terms) { if (term.TotalDegree() > 1) { - maxcon += term.Coef() * pow(radius, term.TotalDegree()); + maxcon += term.Coef() * std::pow(radius, term.TotalDegree()); } } return maxcon; } -template gPoly gPoly::Normalize() const +template Polynomial Polynomial::Normalize() const { - auto maxcoeff = - std::max_element(Terms.begin(), Terms.end(), - [](const gMono &a, const gMono &b) { return a.Coef() < b.Coef(); }); + auto maxcoeff = std::max_element( + m_terms.begin(), m_terms.end(), + [](const Monomial &a, const Monomial &b) { return a.Coef() < b.Coef(); }); if (maxcoeff->Coef() < static_cast(0.000000001)) { return *this; } return *this / maxcoeff->Coef(); } + +} // end namespace Gambit diff --git a/src/solvers/enumpoly/polyfeasible.h b/src/solvers/enumpoly/polyfeasible.h new file mode 100644 index 000000000..34ca13e4b --- /dev/null +++ b/src/solvers/enumpoly/polyfeasible.h @@ -0,0 +1,107 @@ +// +// This file is part of Gambit +// Copyright (c) 1994-2024, The Gambit Project (http://www.gambit-project.org) +// +// FILE: src/solvers/enumpoly/polyfeasible.h +// Check feasibility of a system of polynomial inequalities +// +// 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 POLYFEASIBLE_H +#define POLYFEASIBLE_H + +#include "gambit.h" + +#include "rectangle.h" +#include "polysystem.h" +#include "polypartial.h" + +namespace Gambit { + +/// Determine whether a +/// system of weak inequalities has a solution (a point where all are satisfied) +/// in a given rectangle. Ir is modeled on PolynomialSystemSolver, but simpler. There is +/// no Newton search, only repeated subdivision, queries at the center, and +/// tests against whether one of the inequalities is provably everywhere +/// negative in the rectangle. +/// The constructor for this takes a list of polynomials, interpreted as +/// inequalities in the sense that, at a solution, all the polynomials +/// are required to be non-negative. +class PolynomialFeasibilitySolver { +private: + PolynomialSystem m_system; + PolynomialSystemDerivatives m_systemDerivs; + double m_epsilon; + + bool IsASolution(const Vector &v) const + { + return std::all_of(m_system.begin(), m_system.end(), + [&](const Polynomial &p) { return p.Evaluate(v) > -m_epsilon; }); + } + bool SystemHasNoSolutionIn(const Rectangle &r, Array &precedence) const + { + for (int i = 1; i <= m_system.size(); i++) { + if (m_systemDerivs[precedence[i]].PolyEverywhereNegativeIn(r)) { + if (i != 1) { // We have found a new "most likely to never be positive" + int tmp = precedence[i]; + for (int j = 1; j <= i - 1; j++) { + precedence[i - j + 1] = precedence[i - j]; + } + precedence[1] = tmp; + } + return true; + } + } + return false; + } + + bool SolutionExists(const Rectangle &r, Array &precedence) const + { + if (IsASolution(r.Center())) { + return true; + } + if (SystemHasNoSolutionIn(r, precedence)) { + return false; + } + for (const auto &cell : r.Orthants()) { + if (SolutionExists(cell, precedence)) { + return true; + } + } + return false; + } + +public: + explicit PolynomialFeasibilitySolver(const PolynomialSystem &given) + : m_system(given), m_systemDerivs(given), m_epsilon(1.0e-6) + { + } + PolynomialFeasibilitySolver(const PolynomialFeasibilitySolver &) = delete; + ~PolynomialFeasibilitySolver() = default; + PolynomialFeasibilitySolver &operator=(const PolynomialFeasibilitySolver &) = delete; + + /// Does a solution exist in the specified rectangle? + bool HasSolution(const Rectangle &r) + { + Array precedence(m_system.size()); + std::iota(precedence.begin(), precedence.end(), 1); + return SolutionExists(r, precedence); + } +}; + +} // namespace Gambit + +#endif // POLYFEASIBLE_H diff --git a/src/solvers/enumpoly/polypartial.h b/src/solvers/enumpoly/polypartial.h new file mode 100644 index 000000000..47e738b2f --- /dev/null +++ b/src/solvers/enumpoly/polypartial.h @@ -0,0 +1,97 @@ +// +// This file is part of Gambit +// Copyright (c) 1994-2024, The Gambit Project (http://www.gambit-project.org) +// +// FILE: src/solvers/enumpoly/polypartial.h +// Efficient representation of partial derivatives of polynomials +// +// 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 POLYPARTIAL_H +#define POLYPARTIAL_H + +#include "gambit.h" +#include "rectangle.h" +#include "polysystem.h" + +namespace Gambit { + +template class PolynomialDerivatives { +private: + struct Node { + public: + Polynomial data; + std::list children; + + Node(const Polynomial &p_data) : data(p_data) {} + Node(const Node &) = default; + ~Node() = default; + }; + + Node m_treeroot; + + void BuildTree(Node &); + T MaximalNonconstantContribution(const Node &, const Vector &, const Vector &, + Vector &) const; + +public: + explicit PolynomialDerivatives(const Polynomial &given) : m_treeroot(given) + { + BuildTree(m_treeroot); + } + PolynomialDerivatives(const PolynomialDerivatives &) = default; + ~PolynomialDerivatives() = default; + + int GetDimension() const { return m_treeroot.data.GetDimension(); } + + T MaximalNonconstantContribution(const Vector &, const Vector &) const; + + T ValueOfRootPoly(const Vector &point) const { return m_treeroot.data.Evaluate(point); } + T ValueOfPartialOfRootPoly(int, const Vector &) const; + bool PolyHasNoRootsIn(const Rectangle &) const; + bool MultiaffinePolyHasNoRootsIn(const Rectangle &) const; + bool PolyEverywhereNegativeIn(const Rectangle &) const; + bool MultiaffinePolyEverywhereNegativeIn(const Rectangle &) const; +}; + +template class PolynomialSystemDerivatives { +private: + std::list> m_system; + +public: + explicit PolynomialSystemDerivatives(const PolynomialSystem &given) + { + for (const auto &p : given) { + m_system.push_back(PolynomialDerivatives(p)); + } + } + PolynomialSystemDerivatives(const PolynomialSystemDerivatives &) = default; + ~PolynomialSystemDerivatives() = default; + PolynomialSystemDerivatives &operator=(const PolynomialSystemDerivatives &) = delete; + + const PolynomialDerivatives &operator[](const int index) const + { + return *std::next(m_system.begin(), index - 1); + } + int GetDimension() const { return m_system.front().GetDimension(); } + Matrix DerivativeMatrix(const Vector &, int) const; + SquareMatrix SquareDerivativeMatrix(const Vector &) const; + Vector ValuesOfRootPolys(const Vector &, int) const; +}; + +} // namespace Gambit + +#endif // POLYPARTIAL_H diff --git a/src/solvers/enumpoly/polypartial.imp b/src/solvers/enumpoly/polypartial.imp new file mode 100644 index 000000000..f500eabd5 --- /dev/null +++ b/src/solvers/enumpoly/polypartial.imp @@ -0,0 +1,199 @@ +// +// This file is part of Gambit +// Copyright (c) 1994-2024, The Gambit Project (http://www.gambit-project.org) +// +// FILE: src/solver/enumpoly/polypartial.imp +// Implementation of partial derivatives of polynomials +// +// 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 "polypartial.h" +#include "indexproduct.h" + +namespace Gambit { + +/// Recursive generation of all partial derivatives of the root polynomial +template void PolynomialDerivatives::BuildTree(Node &n) +{ + if (n.data.Degree() >= 1) { + for (int i = 1; i <= n.data.GetDimension(); i++) { + n.children.push_back(n.data.PartialDerivative(i)); + BuildTree(n.children.back()); + } + } +} + +template +T PolynomialDerivatives::ValueOfPartialOfRootPoly(const int coord, const Vector &p) const +{ + if (m_treeroot.data.Degree() <= 0) { + return static_cast(0); + } + return std::next(m_treeroot.children.begin(), coord - 1)->data.Evaluate(p); +} + +template +T PolynomialDerivatives::MaximalNonconstantContribution(const Node &n, const Vector &p, + const Vector &halvesoflengths, + Vector &wrtos) const +{ + T answer = static_cast(0); + int i = 1; + for (const auto child : n.children) { + wrtos[i]++; + + T increment = Gambit::abs(child.data.Evaluate(p)); + for (int j = 1; j <= p.size(); j++) { + for (int k = 1; k <= wrtos[j]; k++) { + increment *= halvesoflengths[j]; + increment /= static_cast(k); + } + } + answer += increment; + answer += MaximalNonconstantContribution(child, p, halvesoflengths, wrtos); + wrtos[i]--; + i++; + } + return answer; +} + +template +T PolynomialDerivatives::MaximalNonconstantContribution(const Vector &p, + const Vector &halvesoflengths) const +{ + Vector WithRespectTos(p.size()); + std::fill(WithRespectTos.begin(), WithRespectTos.end(), 0); + return MaximalNonconstantContribution(m_treeroot, p, halvesoflengths, WithRespectTos); +} + +template bool PolynomialDerivatives::PolyHasNoRootsIn(const Rectangle &r) const +{ + if (m_treeroot.data.IsMultiaffine()) { + return MultiaffinePolyHasNoRootsIn(r); + } + Vector center = r.Center(); + T constant = Gambit::abs(m_treeroot.data.Evaluate(center)); + Vector HalvesOfSideLengths = r.SideLengths() / 2; + return MaximalNonconstantContribution(center, HalvesOfSideLengths) < constant; +} + +template +bool PolynomialDerivatives::MultiaffinePolyHasNoRootsIn(const Rectangle &r) const +{ + T sign = (m_treeroot.data.Evaluate(r.Center()) > static_cast(0)) ? static_cast(1) + : static_cast(-1); + + Array zeros(GetDimension()); + std::fill(zeros.begin(), zeros.end(), 0); + Array ones(GetDimension()); + for (int j = 1; j <= GetDimension(); j++) { + ones[j] = (m_treeroot.data.DegreeOfVar(j) > 0) ? 1 : 0; + } + + for (const auto &topbottoms : CartesianIndexProduct(zeros, ones)) { + Vector point(GetDimension()); + for (int i = 1; i <= GetDimension(); i++) { + point[i] = (topbottoms[i] == 0) ? r.Side(i).LowerBound() : r.Side(i).UpperBound(); + } + if (sign * m_treeroot.data.Evaluate(point) <= static_cast(0)) { + return false; + } + } + return true; +} + +template +bool PolynomialDerivatives::MultiaffinePolyEverywhereNegativeIn(const Rectangle &r) const +{ + if (GetDimension() == 0) { + Vector point(GetDimension()); + return m_treeroot.data.Evaluate(point) < static_cast(0); + } + + Array zeros(GetDimension()); + std::fill(zeros.begin(), zeros.end(), 0); + Array ones(GetDimension()); + for (int j = 1; j <= GetDimension(); j++) { + ones[j] = (m_treeroot.data.DegreeOfVar(j) > 0) ? 1 : 0; + } + + for (const auto &topbottoms : CartesianIndexProduct(zeros, ones)) { + Vector point(GetDimension()); + for (int i = 1; i <= GetDimension(); i++) { + point[i] = (topbottoms[i] == 0) ? r.Side(i).LowerBound() : r.Side(i).UpperBound(); + } + if (m_treeroot.data.Evaluate(point) >= static_cast(0)) { + return false; + } + } + return true; +} + +template +bool PolynomialDerivatives::PolyEverywhereNegativeIn(const Rectangle &r) const +{ + if (m_treeroot.data.IsMultiaffine()) { + return MultiaffinePolyEverywhereNegativeIn(r); + } + auto center = r.Center(); + T constant = m_treeroot.data.Evaluate(center); + if (constant >= static_cast(0)) { + return false; + } + return (MaximalNonconstantContribution(center, r.SideLengths() / 2) + constant < + static_cast(0)); +} + +template +Matrix PolynomialSystemDerivatives::DerivativeMatrix(const Vector &p, + const int p_howmany) const +{ + Matrix answer(p_howmany, GetDimension()); + auto poly = m_system.begin(); + for (int i = 1; i <= p_howmany; i++, poly++) { + for (int j = 1; j <= GetDimension(); j++) { + answer(i, j) = poly->ValueOfPartialOfRootPoly(j, p); + } + } + return answer; +} + +template +SquareMatrix PolynomialSystemDerivatives::SquareDerivativeMatrix(const Vector &p) const +{ + SquareMatrix answer(GetDimension()); + auto poly = m_system.begin(); + for (int i = 1; i <= GetDimension(); i++, poly++) { + for (int j = 1; j <= GetDimension(); j++) { + answer(i, j) = poly->ValueOfPartialOfRootPoly(j, p); + } + } + return answer; +} + +template +Vector PolynomialSystemDerivatives::ValuesOfRootPolys(const Vector &point, + const int p_howmany) const +{ + Vector answer(p_howmany); + auto poly = m_system.begin(); + for (int i = 1; i <= p_howmany; i++, poly++) { + answer[i] = poly->ValueOfRootPoly(point); + } + return answer; +} + +} // end namespace Gambit diff --git a/src/solvers/enumpoly/polysolver.cc b/src/solvers/enumpoly/polysolver.cc new file mode 100644 index 000000000..9bd5624e1 --- /dev/null +++ b/src/solvers/enumpoly/polysolver.cc @@ -0,0 +1,312 @@ +// +// This file is part of Gambit +// Copyright (c) 1994-2024, The Gambit Project (http://www.gambit-project.org) +// +// FILE: src/solvers/enumpoly/polysolver.cc +// Implementation of polynomial system solver +// +// 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 "polysolver.h" +#include "indexproduct.h" + +namespace Gambit { + +RectArray PolynomialSystemSolver::BuildEquationVariableMatrix() const +{ + RectArray answer(m_system.size(), GetDimension()); + for (int i = 1; i <= m_system.size(); i++) { + for (int j = 1; j <= GetDimension(); j++) { + if (m_system[i].DegreeOfVar(j) > 0) { + answer(i, j) = true; + } + else { + answer(i, j) = false; + } + } + } + return answer; +} + +bool PolynomialSystemSolver::SystemHasNoRootsIn(const Rectangle &r, + Array &precedence) const +{ + for (int i = 1; i <= m_system.size(); i++) { + if ((precedence[i] <= NumEquations() && m_derivatives[precedence[i]].PolyHasNoRootsIn(r)) || + (precedence[i] > NumEquations() && + m_derivatives[precedence[i]].PolyEverywhereNegativeIn(r))) { + if (i != 1) { // We have found a new "most likely to never vanish" + int tmp = precedence[i]; + for (int j = 1; j <= i - 1; j++) { + precedence[i - j + 1] = precedence[i - j]; + } + precedence[1] = tmp; + } + return true; + } + } + return false; +} + +//-------------------------------------- +// Does Newton's method lead to a root? +//-------------------------------------- + +// +// In NewtonRootInRectangle(), problems with infinite looping will occur +// due to roundoff in trying to compare to the zero vector. +// The fuzzy_equals() functions below alleviate this by introducing +// an epsilon fudge-factor. This fudge-factor, and indeed the entire +// implementation, is largely ad-hoc, but appears to work for most +// applications. +// + +namespace { + +static bool fuzzy_equals(double x, double y) +{ + const double epsilon = 0.000000001; + + if (x == 0.0) { + return fabs(y) < epsilon; + } + if (y == 0.0) { + return fabs(x) < epsilon; + } + return ((fabs(x - y) / (fabs(x) + fabs(y)) < epsilon) || + (fabs(x) < epsilon && fabs(y) < epsilon)); +} + +} // end anonymous namespace + +bool PolynomialSystemSolver::NewtonRootInRectangle(const Rectangle &r, + Vector &point) const +{ + Vector zero(GetDimension()); + zero = 0; + + Vector oldevals = m_derivatives.ValuesOfRootPolys(point, NumEquations()); + if (std::equal(oldevals.begin(), oldevals.end(), zero.begin(), fuzzy_equals)) { + return r.Contains(point); + } + + Rectangle bigr = r.SameCenterDoubleLengths(); + + Vector newpoint(GetDimension()); + + while (true) { + try { + newpoint = NewtonPolishOnce(point); + } + catch (SingularMatrixException &) { + bool nonsingular = false; + int direction = 1; + while (direction < GetDimension() && !nonsingular) { + Vector perturbed_point(point); + if (r.Side(direction).UpperBound() > point[direction]) { + perturbed_point[direction] += (r.Side(direction).UpperBound() - point[direction]) / 10; + } + else { + perturbed_point[direction] += (r.Side(direction).LowerBound() - point[direction]) / 10; + } + nonsingular = true; + + try { + newpoint = point + (NewtonPolishOnce(perturbed_point) - perturbed_point); + } + catch (SingularMatrixException &) { + nonsingular = false; + } + direction++; + } + if (!nonsingular) { + Vector perturbed_point(point); + if (r.Side(direction).UpperBound() > point[direction]) { + perturbed_point[direction] += (r.Side(direction).UpperBound() - point[direction]) / 10; + } + else { + perturbed_point[direction] += (r.Side(direction).LowerBound() - point[direction]) / 10; + } + newpoint = point + (NewtonPolishOnce(perturbed_point) - perturbed_point); + } + } + + if (!bigr.Contains(newpoint)) { + return false; + } + point = newpoint; + + Vector newevals = m_derivatives.ValuesOfRootPolys(point, NumEquations()); + if (newevals * newevals >= oldevals * oldevals) { + return false; + } + if (std::equal(newevals.begin(), newevals.end(), zero.begin(), fuzzy_equals)) { + if (r.Contains(point)) { + point = SlowNewtonPolishOnce(point); + point = SlowNewtonPolishOnce(point); + return true; + } + return false; + } + oldevals = newevals; + } +} + +double PolynomialSystemSolver::MaxDistanceFromPointToVertexAfterTransformation( + const Rectangle &r, const Vector &p, const SquareMatrix &M) const +{ + // A very early implementation of this method used a type gDouble which + // implemented fuzzy comparisons. Adding the epsilon parameter here is + // important for the case when a solution may be found on the boundary + // of the rectangle but be slightly outside due to numerical error. + if (!r.Contains(p, 1.0e-8)) { + throw AssertionException( + "Point not in rectangle in MaxDistanceFromPointToVertexAfterTransformation."); + } + double max_length = 0.0; + + Array bottom(GetDimension()), top(GetDimension()); + std::fill(bottom.begin(), bottom.end(), 1); + std::fill(top.begin(), top.end(), 2); + for (const auto &topbottoms : CartesianIndexProduct(bottom, top)) { + Vector diffs(GetDimension()); + for (int i = 1; i <= GetDimension(); i++) { + if (topbottoms[i] == 2) { + diffs[i] = r.Side(i).UpperBound() - p[i]; + } + else { + diffs[i] = p[i] - r.Side(i).LowerBound(); + } + } + Vector new_diffs = M * diffs; + max_length = std::max(max_length, new_diffs * new_diffs); + } + return std::sqrt(max_length); +} + +bool PolynomialSystemSolver::HasNoOtherRootsIn(const Rectangle &r, const Vector &p, + const SquareMatrix &M) const +{ + auto system2 = m_normalizedSystem.Translate(p).TransformCoords(M); + double radius = MaxDistanceFromPointToVertexAfterTransformation(r, p, M); + return (std::accumulate(system2.begin(), system2.end(), 0.0, + [&](double v, const Polynomial &poly) { + return v + poly.MaximalValueOfNonlinearPart(radius); + }) < radius); +} + +/// Does Newton's method yield a unique root? +bool PolynomialSystemSolver::NewtonRootIsOnlyInRct(const Rectangle &r, + Vector &point) const +{ + return (NewtonRootInRectangle(r, point) && + HasNoOtherRootsIn(r, point, m_derivatives.SquareDerivativeMatrix(point).Inverse())); +} + +Vector PolynomialSystemSolver::NewtonPolishOnce(const Vector &point) const +{ + Vector oldevals = m_derivatives.ValuesOfRootPolys(point, NumEquations()); + Matrix Df = m_derivatives.DerivativeMatrix(point, NumEquations()); + return point - (Df.Transpose() * SquareMatrix(Df * Df.Transpose()).Inverse()) * oldevals; +} + +Vector PolynomialSystemSolver::SlowNewtonPolishOnce(const Vector &point) const +{ + Vector oldevals = m_derivatives.ValuesOfRootPolys(point, NumEquations()); + Matrix Df = m_derivatives.DerivativeMatrix(point, NumEquations()); + Vector Del = + -(Df.Transpose() * SquareMatrix(Df * Df.Transpose()).Inverse()) * oldevals; + + while (true) { + Vector newevals(m_derivatives.ValuesOfRootPolys(point + Del, NumEquations())); + if (newevals * newevals <= oldevals * oldevals) { + return point + Del; + } + Del /= 2; + } +} + +bool PolynomialSystemSolver::FindRoots(const Rectangle &r, const int max_roots) +{ + const int MAX_ITER = 100000; + m_roots = List>(); + + if (NumEquations() == 0) { + m_roots.push_back(Vector(0)); + return true; + } + + Array precedence(m_system.size()); + // Orders search for nonvanishing poly + std::iota(precedence.begin(), precedence.end(), 1); + + int iterations = 0; + FindRoots(m_roots, r, MAX_ITER, precedence, iterations, 1, max_roots); + return iterations < MAX_ITER; +} + +void PolynomialSystemSolver::FindRoots(List> &rootlist, const Rectangle &r, + int max_iterations, Array &precedence, int &iterations, + int depth, int max_roots) const +{ + // + // TLT: In some cases, this recursive process apparently goes into an + // infinite regress. I'm not able to identify just why this occurs, + // but as at least a temporary safeguard, we will limit the maximum depth + // of this recursive search. + // + // This limit has been chosen only because it doesn't look like any + // "serious" search (i.e., one that actually terminates with a result) + // will take more than a depth of 32. + // + const int MAX_DEPTH = 32; + + if (SystemHasNoRootsIn(r, precedence)) { + return; + } + + Vector point = r.Center(); + if (NewtonRootIsOnlyInRct(r, point)) { + for (int i = NumEquations() + 1; i <= m_system.size(); i++) { + if (m_derivatives[i].ValueOfRootPoly(point) < 0.0) { + return; + } + } + + bool already_found = false; + for (size_t i = 1; i <= rootlist.size(); i++) { + if (std::equal(point.begin(), point.end(), rootlist[i].begin(), fuzzy_equals)) { + already_found = true; + } + } + if (!already_found) { + rootlist.push_back(point); + } + return; + } + + for (const auto &cell : r.Orthants()) { + if (max_roots == 0 || rootlist.size() < max_roots) { + if (iterations >= max_iterations || depth == MAX_DEPTH) { + return; + } + iterations++; + FindRoots(rootlist, cell, max_iterations, precedence, iterations, depth + 1, max_roots); + } + } +} + +} // end namespace Gambit diff --git a/src/solvers/enumpoly/polysolver.h b/src/solvers/enumpoly/polysolver.h new file mode 100644 index 000000000..cef4d1481 --- /dev/null +++ b/src/solvers/enumpoly/polysolver.h @@ -0,0 +1,115 @@ +// +// This file is part of Gambit +// Copyright (c) 1994-2024, The Gambit Project (http://www.gambit-project.org) +// +// FILE: src/solvers/enumpoly/polysolver.h +// Interface to polynomial system solver +// +// 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 POLYSOLVER_H +#define POLYSOLVER_H + +#include "gambit.h" +#include "rectangle.h" +#include "polysystem.h" +#include "polypartial.h" + +namespace Gambit { + +/// @brief Find the roots of a system of polynomials and inequalities, with +/// equal numbers of equations and unknowns, that lie inside a given +/// rectangle. +/// +/// The general idea is to first ask whether the Taylor's +/// series information at the center of the rectangle precludes the +/// existence of roots, and if it does not, whether Newton's method leads +/// to a root, and if it does, whether the Taylor's series information at +/// the root precludes the existence of another root. If the roots in the +/// rectangle are not resolved by these queries, the rectangle is +/// subdivided into 2^d subrectangles, and the process is repeated on +/// each. This continues until it has been shown that all roots have been +/// found, or a predetermined search depth is reached. The bound on depth +/// is necessary because the procedure will not terminate if there are +/// singular roots. + +/// The list of polynomials must +/// be at least as long as the dimension GetDimension() of the space of the +/// system. The first GetDimension() polynomials are interpreted as equations, +/// while remaining polynomials are interpreted as inequalities in the +/// sense that the polynomial is required to be non-negative. +class PolynomialSystemSolver { +private: + PolynomialSystem m_system, m_normalizedSystem; + PolynomialSystemDerivatives m_derivatives; + List> m_roots; + RectArray eq_i_uses_j; + + // Supporting routines for the constructors + + RectArray BuildEquationVariableMatrix() const; + + // Check whether roots are impossible + + bool SystemHasNoRootsIn(const Rectangle &r, Array &) const; + + // Ask whether Newton's method leads to a root + + bool NewtonRootInRectangle(const Rectangle &, Vector &) const; + + // Ask whether we can prove that there is no root other than + // the one produced by the last step + + double MaxDistanceFromPointToVertexAfterTransformation(const Rectangle &, + const Vector &, + const SquareMatrix &) const; + + bool HasNoOtherRootsIn(const Rectangle &, const Vector &, + const SquareMatrix &) const; + + // Combine the last two steps into a single query + bool NewtonRootIsOnlyInRct(const Rectangle &, Vector &) const; + + void FindRoots(List> &, const Rectangle &, int, Array &, + int &iterations, int depth, int) const; + +public: + explicit PolynomialSystemSolver(const PolynomialSystem &p_system) + : m_system(p_system), m_normalizedSystem(p_system.Normalize()), + m_derivatives(m_normalizedSystem), eq_i_uses_j(BuildEquationVariableMatrix()) + { + } + PolynomialSystemSolver(const PolynomialSystemSolver &) = delete; + ~PolynomialSystemSolver() = default; + + PolynomialSystemSolver &operator=(const PolynomialSystemSolver &) = delete; + + // Information + int GetDimension() const { return m_system.GetDimension(); } + int NumEquations() const { return std::min(m_system.GetDimension(), m_system.size()); } + const List> &RootList() const { return m_roots; } + + // Refines the accuracy of roots obtained from other algorithms + Vector NewtonPolishOnce(const Vector &) const; + Vector SlowNewtonPolishOnce(const Vector &) const; + + // Find up to `max_roots` roots inside rectangle `r` + bool FindRoots(const Rectangle &r, int max_roots); +}; + +} // end namespace Gambit + +#endif // POLYSOLVER_H diff --git a/src/solvers/enumpoly/polysystem.h b/src/solvers/enumpoly/polysystem.h new file mode 100644 index 000000000..98a469f71 --- /dev/null +++ b/src/solvers/enumpoly/polysystem.h @@ -0,0 +1,113 @@ +// +// This file is part of Gambit +// Copyright (c) 1994-2024, The Gambit Project (http://www.gambit-project.org) +// +// FILE: src/solvers/enumpoly/polysystem.h +// Representation of a system (collection) of polynomials +// +// 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 POLYSYSTEM_H +#define POLYSYSTEM_H + +#include "poly.h" + +namespace Gambit { + +template class PolynomialSystem { +private: + std::shared_ptr m_space; + std::list> m_system; + +public: + using iterator = typename List>::iterator; + using const_iterator = typename List>::const_iterator; + + PolynomialSystem(std::shared_ptr p_space) : m_space(p_space) {} + PolynomialSystem(const PolynomialSystem &) = default; + ~PolynomialSystem() = default; + PolynomialSystem &operator=(const PolynomialSystem &) = default; + + iterator begin() { return m_system.begin(); } + const_iterator begin() const { return m_system.begin(); } + iterator end() { return m_system.end(); } + const_iterator end() const { return m_system.end(); } + const_iterator cbegin() const { return m_system.cbegin(); } + const_iterator cend() const { return m_system.cend(); } + + bool operator==(const PolynomialSystem &rhs) const + { + return m_space == rhs.m_space && m_system == rhs.m_system; + } + bool operator!=(const PolynomialSystem &rhs) const + { + return m_space != rhs.m_space || m_system != rhs.m_system; + } + void push_back(const Polynomial &x) { m_system.push_back(x); } + void push_back(const PolynomialSystem &x) + { + for (const auto &p : x.m_system) { + m_system.push_back(p); + } + } + + const Polynomial &operator[](const int index) const + { + return *std::next(m_system.begin(), index - 1); + } + + int size() const { return m_system.size(); } + int GetDimension() const { return m_space->GetDimension(); } + /// Evaluate system at a point + Vector Evaluate(const Vector &v) const + { + Vector answer(m_system.size()); + std::transform(m_system.begin(), m_system.end(), answer.begin(), + [&](const Polynomial &p) { return p.Evaluate(v); }); + return answer; + } + /// Translate system to new origin + PolynomialSystem Translate(const Vector &new_origin) const + { + PolynomialSystem ret(m_space); + for (const auto &v : m_system) { + ret.m_system.push_back(v.TranslateOfPoly(new_origin)); + } + return ret; + } + /// Transform system to new coordinates + PolynomialSystem TransformCoords(const SquareMatrix &M) const + { + PolynomialSystem ret(m_space); + for (const auto &v : m_system) { + ret.m_system.push_back(v.PolyInNewCoordinates(M)); + } + return ret; + } + /// Normalize system + PolynomialSystem Normalize() const + { + PolynomialSystem ret(m_space); + for (const auto &v : m_system) { + ret.m_system.push_back(v.Normalize()); + } + return ret; + } +}; + +} // end namespace Gambit + +#endif // POLYSYSTEM_H diff --git a/src/solvers/enumpoly/quiksolv.cc b/src/solvers/enumpoly/quiksolv.cc deleted file mode 100644 index a2df5fbfb..000000000 --- a/src/solvers/enumpoly/quiksolv.cc +++ /dev/null @@ -1,25 +0,0 @@ -// -// This file is part of Gambit -// Copyright (c) 1994-2024, The Gambit Project (http://www.gambit-project.org) -// -// FILE: src/tools/enumpoly/quiksolv.cc -// Instantiations of quick-solver classes -// -// This program is free software; you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation; either version 2 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -// - -#include "quiksolv.imp" - -template class QuikSolv; diff --git a/src/solvers/enumpoly/quiksolv.h b/src/solvers/enumpoly/quiksolv.h deleted file mode 100644 index da246b700..000000000 --- a/src/solvers/enumpoly/quiksolv.h +++ /dev/null @@ -1,141 +0,0 @@ -// -// This file is part of Gambit -// Copyright (c) 1994-2024, The Gambit Project (http://www.gambit-project.org) -// -// FILE: src/tools/enumpoly/quiksolv.h -// Interface to quick-solver classes -// -// This program is free software; you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation; either version 2 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -// - -#ifndef QUIKSOLV_H -#define QUIKSOLV_H - -#include "gambit.h" -#include "odometer.h" -#include "rectangle.h" -#include "gpoly.h" -#include "gpolylst.h" -#include "gpartltr.h" - -/* - The (optimistically named) class described in this file is a method -of finding the roots of a system of polynomials and inequalities, with -equal numbers of equations and unknowns, that lie inside a given -rectangle. The general idea is to first ask whether the Taylor's -series information at the center of the rectangle precludes the -existence of roots, and if it does not, whether Newton's method leads -to a root, and if it does, whether the Taylor's series information at -the root precludes the existence of another root. If the roots in the -rectangle are not resolved by these queries, the rectangle is -subdivided into 2^d subrectangles, and the process is repeated on -each. This continues until it has been shown that all roots have been -found, or a predetermined search depth is reached. The bound on depth -is necessary because the procedure will not terminate if there are -singular roots. -*/ - -/* - The main constructor for this takes a gPolyList. The list must -be at least as long as the dimension Dmnsn() of the space of the -system. The first Dmnsn() polynomials are interpreted as equations, -while remaining polynomials are interpreted as inequalities in the -sense that the polynomial is required to be nonnegative. -*/ - -/* - * The original implementation of this used a custom floating-point - * class as its parameter T, which implemented fuzzy comparisons. - * This has since been rewritten such that it uses regular floating - * point with explicit tolerances; this did introduce some subtle - * bugs originally and it is possible some still remain. - */ - -// *********************** -// class QuikSolv -// *********************** - -template class QuikSolv { -private: - const gPolyList System; - const gPolyList gDoubleSystem; - const int NoEquations; - const int NoInequalities; - const ListOfPartialTrees TreesOfPartials; - bool HasBeenSolved; - Gambit::List> Roots; - const bool isMultiaffine; - const Gambit::RectArray Equation_i_uses_var_j; - - // Supporting routines for the constructors - - Gambit::RectArray Eq_i_Uses_j() const; - - // Check whether roots are impossible - - bool SystemHasNoRootsIn(const Rectangle &r, Gambit::Array &) const; - - // Ask whether Newton's method leads to a root - - bool NewtonRootInRectangle(const Rectangle &, Gambit::Vector &) const; - - // Ask whether we can prove that there is no root other than - // the one produced by the last step - - double - MaxDistanceFromPointToVertexAfterTransformation(const Rectangle &, - const Gambit::Vector &, - const Gambit::SquareMatrix &) const; - - bool HasNoOtherRootsIn(const Rectangle &, const Gambit::Vector &, - const Gambit::SquareMatrix &) const; - - // Combine the last two steps into a single query - - bool NewtonRootIsOnlyInRct(const Rectangle &, Gambit::Vector &) const; - - // Recursive parts of recursive methods - - void FindRootsRecursion(Gambit::List> *, const Rectangle &, - const int &, Gambit::Array &, int &iterations, int depth, - const int &, int *) const; - -public: - explicit QuikSolv(const gPolyList &); - QuikSolv(const QuikSolv &); - ~QuikSolv() = default; - - // Operators - QuikSolv &operator=(const QuikSolv &); - bool operator==(const QuikSolv &) const; - bool operator!=(const QuikSolv &) const; - - // Information - inline const VariableSpace *AmbientSpace() const { return System.AmbientSpace(); } - inline int Dmnsn() const { return System.Dmnsn(); } - inline const gPolyList &UnderlyingEquations() const { return System; } - inline bool WasSolved() const { return HasBeenSolved; } - inline const Gambit::List> &RootList() const { return Roots; } - inline bool IsMultiaffine() const { return isMultiaffine; } - - // Refines the accuracy of roots obtained from other algorithms - Gambit::Vector NewtonPolishOnce(const Gambit::Vector &) const; - Gambit::Vector SlowNewtonPolishOnce(const Gambit::Vector &) const; - - // The grand calculation - returns true if successful - bool FindCertainNumberOfRoots(const Rectangle &, const int &, const int &); -}; - -#endif // QUIKSOLV_H diff --git a/src/solvers/enumpoly/quiksolv.imp b/src/solvers/enumpoly/quiksolv.imp deleted file mode 100644 index 48afd5969..000000000 --- a/src/solvers/enumpoly/quiksolv.imp +++ /dev/null @@ -1,483 +0,0 @@ -// -// This file is part of Gambit -// Copyright (c) 1994-2024, The Gambit Project (http://www.gambit-project.org) -// -// FILE: src/tools/enumpoly/quiksolv.imp -// Implementation of quick-solver classes -// -// This program is free software; you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation; either version 2 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -// - -#include "quiksolv.h" - -//--------------------------------------------------------------- -// class: QuikSolv -//--------------------------------------------------------------- - -//--------------------------- -// Constructors / Destructors -//--------------------------- - -using namespace Gambit; - -template -QuikSolv::QuikSolv(const gPolyList &given) - : System(given), gDoubleSystem(given.AmbientSpace(), given.NormalizedList()), - NoEquations(std::min(System.Dmnsn(), System.Length())), - NoInequalities(std::max(System.Length() - System.Dmnsn(), 0)), TreesOfPartials(gDoubleSystem), - HasBeenSolved(false), Roots(), isMultiaffine(System.IsMultiaffine()), - Equation_i_uses_var_j(Eq_i_Uses_j()) -{ -} - -template -QuikSolv::QuikSolv(const QuikSolv &qs) - : System(qs.System), gDoubleSystem(qs.gDoubleSystem), NoEquations(qs.NoEquations), - NoInequalities(qs.NoEquations), TreesOfPartials(qs.TreesOfPartials), - HasBeenSolved(qs.HasBeenSolved), Roots(qs.Roots), isMultiaffine(qs.isMultiaffine), - Equation_i_uses_var_j(qs.Equation_i_uses_var_j) - -{ -} - -//------------------------------------------------------- -// Supporting Calculations for the Constructors -//------------------------------------------------------- - -template Gambit::RectArray QuikSolv::Eq_i_Uses_j() const -{ - Gambit::RectArray answer(System.Length(), Dmnsn()); - for (int i = 1; i <= System.Length(); i++) { - for (int j = 1; j <= Dmnsn(); j++) { - if (System[i].DegreeOfVar(j) > 0) { - answer(i, j) = true; - } - else { - answer(i, j) = false; - } - } - } - return answer; -} - -//--------------------------- -// Is a root impossible? -//--------------------------- - -template -bool QuikSolv::SystemHasNoRootsIn(const Rectangle &r, - Gambit::Array &precedence) const -{ - for (int i = 1; i <= System.Length(); i++) { - - if ((precedence[i] <= NoEquations && TreesOfPartials[precedence[i]].PolyHasNoRootsIn(r)) || - (precedence[i] > NoEquations && - TreesOfPartials[precedence[i]].PolyEverywhereNegativeIn(r))) { - if (i != 1) { // We have found a new "most likely to never vanish" - int tmp = precedence[i]; - for (int j = 1; j <= i - 1; j++) { - precedence[i - j + 1] = precedence[i - j]; - } - precedence[1] = tmp; - } - return true; - } - } - return false; -} - -//-------------------------------------- -// Does Newton's method lead to a root? -//-------------------------------------- - -// -// In NewtonRootInRectangle(), problems with infinite looping will occur -// due to roundoff in trying to compare to the zero vector. -// The fuzzy_equals() functions below alleviate this by introducing -// an epsilon fudge-factor. This fudge-factor, and indeed the entire -// implementation, is largely ad-hoc, but appears to work for most -// applications. -// -// The fuzzy_equals() function used to be implemented as operator== -// for the type gDouble in previous versions; indeed, it was the -// raison d'etre for that class' existence. As such, it is possible -// that this technique may be useful elsewhere where gDouble used to -// be used. -// - -static bool fuzzy_equals(double x, double y) -{ - const double epsilon = 0.000000001; - - if (x == 0) { - return (fabs(y) < epsilon); - } - else if (y == 0) { - return (fabs(x) < epsilon); - } - else { - return ((fabs(x - y) / (fabs(x) + fabs(y)) < epsilon) || - (fabs(x) < epsilon && fabs(y) < epsilon)); - } -} - -static bool fuzzy_equals(const Gambit::Vector &x, const Gambit::Vector &y) -{ - for (int i = x.first_index(); i <= x.last_index(); i++) { - if (!fuzzy_equals(x[i], y[i])) { - return false; - } - } - return true; -} - -template -bool QuikSolv::NewtonRootInRectangle(const Rectangle &r, - Gambit::Vector &point) const -{ - // assert (NoEquations == System.Dmnsn()); - - Gambit::Vector zero(Dmnsn()); - for (int i = 1; i <= Dmnsn(); i++) { - zero[i] = (double)0; - } - - Gambit::Vector oldevals = TreesOfPartials.ValuesOfRootPolys(point, NoEquations); - - if (fuzzy_equals(oldevals, zero)) { - return r.Contains(point); - } - - Rectangle bigr = r.SameCenterDoubleLengths(); - - Gambit::Vector newpoint(Dmnsn()); - - while (true) { - try { - newpoint = NewtonPolishOnce(point); - } - catch (Gambit::SingularMatrixException &) { - bool nonsingular = false; - int direction = 1; - while (direction < Dmnsn() && !nonsingular) { - Gambit::Vector perturbed_point(point); - if (r.Side(direction).UpperBound() > point[direction]) { - perturbed_point[direction] += (r.Side(direction).UpperBound() - point[direction]) / 10; - } - else { - perturbed_point[direction] += (r.Side(direction).LowerBound() - point[direction]) / 10; - } - nonsingular = true; - - try { - newpoint = point + (NewtonPolishOnce(perturbed_point) - perturbed_point); - } - catch (Gambit::SingularMatrixException &) { - nonsingular = false; - } - direction++; - } - if (!nonsingular) { - Gambit::Vector perturbed_point(point); - if (r.Side(direction).UpperBound() > point[direction]) { - perturbed_point[direction] += (r.Side(direction).UpperBound() - point[direction]) / 10; - } - else { - perturbed_point[direction] += (r.Side(direction).LowerBound() - point[direction]) / 10; - } - newpoint = point + (NewtonPolishOnce(perturbed_point) - perturbed_point); - } - } - - if (!bigr.Contains(newpoint)) { - return false; - } - point = newpoint; - - Gambit::Vector newevals = TreesOfPartials.ValuesOfRootPolys(point, NoEquations); - if (newevals * newevals >= oldevals * oldevals) { - return false; - } - if (fuzzy_equals(newevals, zero)) { - if (r.Contains(point)) { - point = SlowNewtonPolishOnce(point); - point = SlowNewtonPolishOnce(point); - return true; - } - else { - return false; - } - } - - oldevals = newevals; - } - return false; -} - -//------------------------------------ -// Is the Newton root the only root? -//------------------------------------ - -template -double QuikSolv::MaxDistanceFromPointToVertexAfterTransformation( - const Rectangle &r, const Gambit::Vector &p, - const Gambit::SquareMatrix &M) const -{ - // A very early implementation of this method used a type gDouble which - // implemented fuzzy comparisons. Adding the epsilon parameter here is - // important for the case when a solution may be found on the boundary - // of the rectangle but be slightly outside due to numerical error. - if (!r.Contains(p, T(1.0e-8))) { - throw Gambit::AssertionException( - "Point not in rectangle in MaxDistanceFromPointToVertexAfterTransformation."); - } - auto max = (double)0; - - Gambit::Array ListOfTwos(Dmnsn()); - for (int i = 1; i <= Dmnsn(); i++) { - ListOfTwos[i] = 2; - } - gIndexOdometer ListOfTopBottoms(ListOfTwos); - - while (ListOfTopBottoms.Turn()) { - Gambit::Vector diffs(Dmnsn()); - for (int i = 1; i <= Dmnsn(); i++) { - if (ListOfTopBottoms[i] == 2) { - diffs[i] = r.Side(i).UpperBound() - p[i]; - } - else { - diffs[i] = p[i] - r.Side(i).LowerBound(); - } - } - Gambit::Vector new_diffs = M * diffs; - double squared_length = new_diffs * new_diffs; - if (max < squared_length) { - max = squared_length; - } - } - - return sqrt((double)max); -} - -template -bool QuikSolv::HasNoOtherRootsIn(const Rectangle &r, const Gambit::Vector &p, - const Gambit::SquareMatrix &M) const -{ - // assert (NoEquations == System.Dmnsn()); - gPolyList system1 = gDoubleSystem.TranslateOfSystem(p); - gPolyList system2 = system1.SystemInNewCoordinates(M); - double radius = MaxDistanceFromPointToVertexAfterTransformation(r, p, M); - auto max = (double)0; - for (int i = 1; i <= Dmnsn(); i++) { - max += system2[i].MaximalValueOfNonlinearPart(radius); - } - - if (max >= radius) { - return false; - } - else { - return true; - } -} - -//-------------------------------------------- -// Does Newton's method yield a unique root? -//-------------------------------------------- - -template -bool QuikSolv::NewtonRootIsOnlyInRct(const Rectangle &r, - Gambit::Vector &point) const -{ - // assert (NoEquations == System.Dmnsn()); - - if (NewtonRootInRectangle(r, point)) { - Gambit::SquareMatrix Df = TreesOfPartials.SquareDerivativeMatrix(point); - if (HasNoOtherRootsIn(r, point, Df.Inverse())) { - return true; - } - else { - return false; - } - } - else { - return false; - } -} - -//---------------------------------- -// Operators -//---------------------------------- - -template QuikSolv &QuikSolv::operator=(const QuikSolv &rhs) -{ - // assert (System == rhs.System); - - if (*this != rhs) { - HasBeenSolved = rhs.HasBeenSolved; - Roots = rhs.Roots; - } - return *this; -} - -template bool QuikSolv::operator==(const QuikSolv &rhs) const -{ - if (System != rhs.System || HasBeenSolved != rhs.HasBeenSolved || Roots != rhs.Roots) { - return false; - } - else { - return true; - } -} - -template bool QuikSolv::operator!=(const QuikSolv &rhs) const -{ - return !(*this == rhs); -} - -//------------------------------------------- -// Improve Accuracy of Root -//------------------------------------------- - -template -Gambit::Vector QuikSolv::NewtonPolishOnce(const Gambit::Vector &point) const -{ - Gambit::Vector oldevals = TreesOfPartials.ValuesOfRootPolys(point, NoEquations); - Gambit::Matrix Df = TreesOfPartials.DerivativeMatrix(point, NoEquations); - Gambit::SquareMatrix M(Df * Df.Transpose()); - - Gambit::Vector Del = -(Df.Transpose() * M.Inverse()) * oldevals; - - return point + Del; -} - -template -Gambit::Vector QuikSolv::SlowNewtonPolishOnce(const Gambit::Vector &point) const -{ - Gambit::Vector oldevals = TreesOfPartials.ValuesOfRootPolys(point, NoEquations); - Gambit::Matrix Df = TreesOfPartials.DerivativeMatrix(point, NoEquations); - Gambit::SquareMatrix M(Df * Df.Transpose()); - - Gambit::Vector Del = -(Df.Transpose() * M.Inverse()) * oldevals; - - bool done = false; - while (!done) { - Gambit::Vector newevals(TreesOfPartials.ValuesOfRootPolys(point + Del, NoEquations)); - if (newevals * newevals <= oldevals * oldevals) { - done = true; - } - else { - for (int i = 1; i <= Del.size(); i++) { - Del[i] /= 2; - } - } - } - - return point + Del; -} - -//------------------------------------------- -// The Central Calculation -//------------------------------------------- - -template -bool QuikSolv::FindCertainNumberOfRoots(const Rectangle &r, const int &max_iterations, - const int &max_no_roots) -{ - auto *rootlistptr = new Gambit::List>(); - - if (NoEquations == 0) { - Gambit::Vector answer(0); - rootlistptr->push_back(answer); - Roots = *rootlistptr; - HasBeenSolved = true; - return true; - } - - Gambit::Array precedence(System.Length()); - // Orders search for nonvanishing poly - for (int i = 1; i <= System.Length(); i++) { - precedence[i] = i; - } - - int iterations = 0; - - int *no_found = new int(0); - FindRootsRecursion(rootlistptr, r, max_iterations, precedence, iterations, 1, max_no_roots, - no_found); - - if (iterations < max_iterations) { - Roots = *rootlistptr; - HasBeenSolved = true; - return true; - } - - return false; -} - -template -void QuikSolv::FindRootsRecursion(Gambit::List> *rootlistptr, - const Rectangle &r, const int &max_iterations, - Gambit::Array &precedence, int &iterations, int depth, - const int &max_no_roots, int *roots_found) const -{ - // - // TLT: In some cases, this recursive process apparently goes into an - // infinite regress. I'm not able to identify just why this occurs, - // but as at least a temporary safeguard, we will limit the maximum depth - // of this recursive search. - // - // This limit has been chosen only because it doesn't look like any - // "serious" search (i.e., one that actually terminates with a result) - // will take more than a depth of 32. - // - const int MAX_DEPTH = 32; - - if (SystemHasNoRootsIn(r, precedence)) { - return; - } - - Gambit::Vector point = r.Center(); - - if (NewtonRootIsOnlyInRct(r, point)) { - for (int i = NoEquations + 1; i <= System.Length(); i++) { - if (TreesOfPartials[i].ValueOfRootPoly(point) < (double)0) { - return; - } - } - - bool already_found = false; - for (size_t i = 1; i <= rootlistptr->size(); i++) { - if (fuzzy_equals(point, (*rootlistptr)[i])) { - already_found = true; - } - } - if (!already_found) { - rootlistptr->push_back(point); - (*roots_found)++; - } - return; - } - - for (const auto &cell : r.Orthants()) { - if (max_no_roots == 0 || *roots_found < max_no_roots) { - if (iterations >= max_iterations || depth == MAX_DEPTH) { - return; - } - iterations++; - FindRootsRecursion(rootlistptr, cell, max_iterations, precedence, iterations, depth + 1, - max_no_roots, roots_found); - } - } -} diff --git a/src/solvers/enumpoly/rectangle.h b/src/solvers/enumpoly/rectangle.h index 532161052..73e3dc5a8 100644 --- a/src/solvers/enumpoly/rectangle.h +++ b/src/solvers/enumpoly/rectangle.h @@ -2,8 +2,8 @@ // This file is part of Gambit // Copyright (c) 1994-2024, The Gambit Project (http://www.gambit-project.org) // -// FILE: src/tools/enumpoly/rectangle.h -// Declaration of rectangle class +// FILE: src/solvers/enumpoly/rectangle.h +// A utility class representing a (generalised) rectangle in N-dimensional space // // 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 @@ -25,7 +25,7 @@ #include "gambit.h" -using namespace Gambit; +namespace Gambit { /// A nonempty compact interval template class Interval { @@ -177,4 +177,6 @@ template class Rectangle { } }; +} // end namespace Gambit + #endif // RECTANGLE_H From ac2ab5d2648027da20069b73863556ec969cdc7a Mon Sep 17 00:00:00 2001 From: drdkad Date: Sat, 9 Nov 2024 11:33:54 +0000 Subject: [PATCH 50/99] Implementing functions for reading and writing game files of specialized formats Reading * `read_efg` - reads an .efg file format * `read_nfg` - reads an .nfg file format * `read_gbt` - reads a .gbt file (XML files produced by the GUI) * `read_agg` - reads an action-graph games file format Writing * `Game.to_efg` - writes an .efg file * `Game.to_nfg` - writes an .nfg file * `Game.to_html` - writes out HTML tables * `Game.to_latex` - writes a .tex file New tests added in test_io.py Closes #357. --- .gitignore | 1 + ChangeLog | 3 + doc/pygambit.api.rst | 11 +- src/pygambit/gambit.pxd | 9 ++ src/pygambit/game.pxi | 248 +++++++++++++++++++++++++++++++++++++++ src/pygambit/util.h | 49 ++++++++ tests/test_games/2x2.agg | 49 ++++++++ tests/test_io.py | 91 ++++++++++++++ 8 files changed, 460 insertions(+), 1 deletion(-) create mode 100644 tests/test_games/2x2.agg create mode 100644 tests/test_io.py 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/ChangeLog b/ChangeLog index df9a33e2d..ec171ec20 100644 --- a/ChangeLog +++ b/ChangeLog @@ -24,6 +24,9 @@ - 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 diff --git a/doc/pygambit.api.rst b/doc/pygambit.api.rst index a9f0a6d15..b7eba64af 100644 --- a/doc/pygambit.api.rst +++ b/doc/pygambit.api.rst @@ -28,6 +28,11 @@ 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 @@ -36,6 +41,10 @@ Creating, reading, and writing games Game.read_game Game.parse_game Game.write + Game.to_efg + Game.to_nfg + Game.to_html + Game.to_latex Transforming game trees @@ -181,7 +190,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 diff --git a/src/pygambit/gambit.pxd b/src/pygambit/gambit.pxd index f3bde61a7..96cd23062 100644 --- a/src/pygambit/gambit.pxd +++ b/src/pygambit/gambit.pxd @@ -379,8 +379,17 @@ cdef extern from "games/behavspt.h": cdef extern from "util.h": c_Game ReadGame(char *) except +IOError c_Game ParseGame(char *) except +IOError + 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 WriteGame(c_Game, string) except +IOError string WriteGame(c_StrategySupportProfile) except +IOError + string WriteEfgFile(c_Game) + string WriteNfgFile(c_Game) + string WriteLaTeXFile(c_Game) + string WriteHTMLFile(c_Game) + # string WriteGbtFile(c_Game) c_Rational to_rational(char *) except + diff --git a/src/pygambit/game.pxi b/src/pygambit/game.pxi index 74176b208..371cf6f98 100644 --- a/src/pygambit/game.pxi +++ b/src/pygambit/game.pxi @@ -19,14 +19,144 @@ # 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 warnings import numpy as np import scipy.stats 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.BytesIO], + parser: GameParser): + + g = cython.declare(Game) + if isinstance(filepath_or_buffer, io.BytesIO): + 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.BytesIO]) -> Game: + """Construct a game from its serialised representation in a GBT file. + + Parameters + ---------- + filepath_or_buffer : str, Path or BytesIO + 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.BytesIO]) -> Game: + """Construct a game from its serialised representation in an EFG file. + + Parameters + ---------- + filepath_or_buffer : str, Path or BytesIO + 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.BytesIO]) -> Game: + """Construct a game from its serialised representation in a NFG file. + + Parameters + ---------- + filepath_or_buffer : str, Path or BytesIO + 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.BytesIO]) -> Game: + """Construct a game from its serialised representation in an AGG file. + + Parameters + ---------- + filepath_or_buffer : str, Path or BytesIO + 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 GameOutcomes: @@ -446,6 +576,10 @@ class Game: def read_game(cls, filepath: typing.Union[str, pathlib.Path]) -> Game: """Construct a game from its serialised representation in a file. + .. deprecated:: 16.3.0 + Method `Game.read_game` is deprecated, use one of the respective functions instead: + ``read_gbt``, ``read_efg``, ``read_nfg``, ``read_agg`` + Parameters ---------- filepath : str or path object @@ -467,6 +601,11 @@ class Game: -------- parse_game : Constructs a game from a text string. """ + warnings.warn( + "Game.read_game() is deprecated and will be removed in 16.4. " + "Use the appropriate module-level .read_*() function instead.", + FutureWarning + ) with open(filepath, "rb") as f: data = f.read() try: @@ -1060,9 +1199,118 @@ class Game: .. versionchanged:: 16.3.0 Removed support for writing Game Theory Explorer format as the XML format is no longer supported by recent versions of GTE. + + .. deprecated:: 16.3.0 + Method Game.write is deprecated, use one of the respective methods instead: + ``Game.to_efg``, ``Game.to_nfg``, ``Game.to_html``, ``Game.to_latex`` """ + warnings.warn( + "Game.write() is deprecated and will be removed in 16.4. " + "Use the appropriate Game.to_*() function instead.", + FutureWarning + ) return WriteGame(self.game, format.encode("ascii")).decode("ascii") + @cython.cfunc + def _to_format( + self, + writer: GameWriter, + filepath_or_buffer: typing.Union[str, pathlib.Path, io.BufferedWriter, None] = None + ): + serialized_game = writer(self.game) + if filepath_or_buffer is None: + return serialized_game.decode() + if isinstance(filepath_or_buffer, io.BufferedWriter): + filepath_or_buffer.write(serialized_game) + else: + with open(filepath_or_buffer, "wb") as f: + f.write(serialized_game) + + def to_efg(self, + filepath_or_buffer: typing.Union[str, pathlib.Path, io.BufferedWriter, None] = None + ) -> typing.Union[str, None]: + """Save the game to an .efg 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_nfg, to_html, to_latex + """ + return self._to_format(WriteEfgFile, filepath_or_buffer) + + def to_nfg(self, + filepath_or_buffer: typing.Union[str, pathlib.Path, io.BufferedWriter, 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.BufferedWriter, None] = None + ) -> typing.Union[str, None]: + """Export the game to an .html 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 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.BufferedWriter, None] = None + ) -> typing.Union[str, None]: + """Export the game to a .tex 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 exported to a file + + See Also + -------- + to_efg, to_nfg, to_html + """ + return self._to_format(WriteLaTeXFile, filepath_or_buffer) + def _resolve_player(self, player: typing.Any, funcname: str, argname: str = "player") -> Player: """Resolve an attempt to reference a player of the game. diff --git a/src/pygambit/util.h b/src/pygambit/util.h index 17787282c..cfe0b203e 100644 --- a/src/pygambit/util.h +++ b/src/pygambit/util.h @@ -50,6 +50,55 @@ Game ParseGame(char *s) return ReadGame(f); } +Game ParseGbtGame(std::string const &s) +{ + std::istringstream f(s); + return ReadGbtFile(f); +} + +Game ParseEfgGame(std::string const &s) +{ + std::istringstream f(s); + return ReadEfgFile(f); +} + +Game ParseNfgGame(std::string const &s) +{ + std::istringstream f(s); + return ReadNfgFile(f); +} + +Game ParseAggGame(std::string const &s) +{ + std::istringstream f(s); + return ReadAggFile(f); +} + +std::string WriteEfgFile(const Game &p_game) +{ + std::ostringstream f; + p_game->Write(f, "efg"); + return f.str(); +} + +std::string WriteNfgFile(const Game &p_game) +{ + std::ostringstream f; + p_game->Write(f, "nfg"); + return f.str(); +} + +std::string WriteHTMLFile(const Game &p_game) +{ + return WriteHTMLFile(p_game, p_game->GetPlayer(1), p_game->GetPlayer(2)); +} + +std::string WriteLaTeXFile(const Game &p_game) +{ + return WriteLaTeXFile(p_game, p_game->GetPlayer(1), p_game->GetPlayer(2)); +} + +/// @deprecated Deprecated in favour of WriteXXXFile std::string WriteGame(const Game &p_game, const std::string &p_format) { if (p_format == "html") { diff --git a/tests/test_games/2x2.agg b/tests/test_games/2x2.agg new file mode 100644 index 000000000..3d1ef04d6 --- /dev/null +++ b/tests/test_games/2x2.agg @@ -0,0 +1,49 @@ +#AGG +# Generated by GAMUT v1.0.1 +# Random Symmetric Action Graph Game +# Game Parameter Values: +# Random seed: 1306765487422 +# Cmd Line: -players 2 -actions 2 -g RandomSymmetricAGG -output SpecialOutput -random_params -f 2x2.agg +# Players: 2 +# Actions: 2 2 +# players: 2 +# actions: [2] +# graph: RandomGraph +# graph_params: null +# Graph Params: +# { nodes: 2, edges: 4, sym_edges: false, reflex_ok: true } +# Players: 2 +# Actions: [ 2 2 ] + +#number of players: +2 +#number of action nodes: +2 +#number of func nodes: +0 + +#sizes of action sets: +2 2 + +#action sets: +0 1 +0 1 + + +#the action graph: +2 0 1 +2 1 0 + +#the types of func nodes: +#0: sum +#1: existence +#2: highest +#3: lowest + + +#the payoffs: +#now the payoff values: one row per action node. +#For each row: first, the type of the payoff format +#Then payoffs are given in lexicographical order of the input configurations +0 35.622809717175556 -3.7188980070375948 +0 -10.180526107272556 95.1203958671928 diff --git a/tests/test_io.py b/tests/test_io.py new file mode 100644 index 000000000..8e3536ab3 --- /dev/null +++ b/tests/test_io.py @@ -0,0 +1,91 @@ +import io +import os.path +from glob import glob + +import pytest + +import pygambit as gbt + +from .games import create_2x2_zero_nfg, create_selten_horse_game_efg + + +@pytest.mark.parametrize("game_path", glob(os.path.join("tests", "test_games", "*.efg"))) +def test_read_efg(game_path): + game = gbt.read_efg(game_path) + assert isinstance(game, gbt.Game) + + +def test_read_efg_invalid(): + game_path = os.path.join("tests", "test_games", "2x2x2_nfg_with_two_pure_one_mixed_eq.nfg") + with pytest.raises(ValueError): + gbt.read_efg(game_path) + + +@pytest.mark.parametrize("game_path", glob(os.path.join("tests", "test_games", "*.nfg"))) +def test_read_nfg(game_path): + game = gbt.read_nfg(game_path) + assert isinstance(game, gbt.Game) + + +def test_read_nfg_invalid(): + game_path = os.path.join("tests", "test_games", "cent3.efg") + with pytest.raises(ValueError): + gbt.read_nfg(game_path) + + +@pytest.mark.parametrize("game_path", glob(os.path.join("tests", "test_games", "*.agg"))) +def test_read_agg(game_path): + game = gbt.read_agg(game_path) + assert isinstance(game, gbt.Game) + + +def test_read_agg_invalid(): + game_path = os.path.join("tests", "test_games", "2x2x2_nfg_with_two_pure_one_mixed_eq.nfg") + with pytest.raises(ValueError): + gbt.read_agg(game_path) + + +def test_read_gbt_invalid(): + game_path = os.path.join("tests", "test_games", "2x2x2_nfg_with_two_pure_one_mixed_eq.nfg") + with pytest.raises(ValueError): + gbt.read_gbt(game_path) + + +def test_write_efg(): + game = gbt.Game.new_tree() + serialized_game = game.to_efg() + assert serialized_game[:3] == "EFG" + + +def test_write_nfg(): + game = gbt.Game.new_table([2, 2]) + serialized_game = game.to_nfg() + assert serialized_game[:3] == "NFG" + + +def test_write_html(): + game = gbt.Game.new_table([2, 2]) + serialized_game = game.to_html() + assert isinstance(serialized_game, str) + + +def test_write_latex(): + game = gbt.Game.new_table([2, 2]) + serialized_game = game.to_latex() + assert serialized_game.startswith(r"\begin{game}") + + +def test_read_write_efg(): + efg_game = create_selten_horse_game_efg() + serialized_efg_game = efg_game.to_efg() + deserialized_efg_game = gbt.read_efg(io.BytesIO(serialized_efg_game.encode())) + double_serialized_efg_game = deserialized_efg_game.to_efg() + assert serialized_efg_game == double_serialized_efg_game + + +def test_read_write_nfg(): + nfg_game = create_2x2_zero_nfg() + serialized_nfg_game = nfg_game.to_nfg() + deserialized_nfg_game = gbt.read_nfg(io.BytesIO(serialized_nfg_game.encode())) + double_serialized_nfg_game = deserialized_nfg_game.to_nfg() + assert serialized_nfg_game == double_serialized_nfg_game From 1e40753229be7afa955490d5d5fe81599caf3451 Mon Sep 17 00:00:00 2001 From: Ted Turocy Date: Thu, 9 Jan 2025 11:21:30 +0000 Subject: [PATCH 51/99] Revise tests to use new de-seralisation functions instead of deprecated one. --- tests/games.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/games.py b/tests/games.py index 6fe0ed10a..652dafc73 100644 --- a/tests/games.py +++ b/tests/games.py @@ -5,7 +5,12 @@ def read_from_file(fn: str) -> gbt.Game: - return gbt.Game.read_game(pathlib.Path("tests/test_games")/fn) + if fn.endswith(".efg"): + return gbt.read_efg(pathlib.Path("tests/test_games")/fn) + elif fn.endswith(".nfg"): + return gbt.read_nfg(pathlib.Path("tests/test_games")/fn) + else: + raise ValueError(f"Unknown file extension in {fn}") ################################################################################################ From 8423c079fd6a7dedee3b922ad0c714b20250e010 Mon Sep 17 00:00:00 2001 From: Ted Turocy Date: Thu, 9 Jan 2025 15:30:04 +0000 Subject: [PATCH 52/99] Fix regression in Array --- src/core/array.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/array.h b/src/core/array.h index 140b0e292..2719aa172 100644 --- a/src/core/array.h +++ b/src/core/array.h @@ -196,7 +196,7 @@ template class Array { { if (this != &a) { // We only reallocate if necessary. - if (!m_data || (m_data && (m_offset != a.m_offset || m_length == a.m_length))) { + if (!m_data || (m_data && (m_offset != a.m_offset || m_length != a.m_length))) { if (m_data) { delete[] (m_data + m_offset); } From c1a222c1dabdbbb810e34114dedbd2e15f233da5 Mon Sep 17 00:00:00 2001 From: Ted Turocy Date: Thu, 9 Jan 2025 12:50:02 +0000 Subject: [PATCH 53/99] Add enumpoly back to GUI --- src/gui/dlnash.cc | 40 +++++++++++++++++----------------------- 1 file changed, 17 insertions(+), 23 deletions(-) diff --git a/src/gui/dlnash.cc b/src/gui/dlnash.cc index 417c5d63d..805602ccb 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) { @@ -209,8 +209,8 @@ gbtAnalysisOutput *gbtNashChoiceDialog::GetCommand() const if (m_doc->NumPlayers() == 2) { cmd = new gbtAnalysisProfileList(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); @@ -222,13 +222,13 @@ gbtAnalysisOutput *gbtNashChoiceDialog::GetCommand() const if (m_doc->NumPlayers() == 2) { cmd = new gbtAnalysisProfileList(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->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); } } } @@ -240,27 +240,22 @@ gbtAnalysisOutput *gbtNashChoiceDialog::GetCommand() const else if (method == s_enummixed) { cmd = new gbtAnalysisProfileList(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->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->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->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); @@ -270,8 +265,7 @@ gbtAnalysisOutput *gbtNashChoiceDialog::GetCommand() const else if (method == s_lcp) { cmd = new gbtAnalysisProfileList(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); @@ -286,7 +280,7 @@ gbtAnalysisOutput *gbtNashChoiceDialog::GetCommand() const else if (method == s_simpdiv) { cmd = new gbtAnalysisProfileList(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! From 010dfb943dac61802c726ddc66b0dae3c18f7736 Mon Sep 17 00:00:00 2001 From: Ted Turocy Date: Fri, 10 Jan 2025 09:26:19 +0000 Subject: [PATCH 54/99] Correct handling of non-invertible matrix in Newton's step in enumpoly In the Newton step in enumpoly, matrices which are singular (or close to singular) would result in unpredictable behaviour for the step, including in some cases failing to find an isolated equilibrium (for example in the standard 2x2x2.nfg example, with full support). This broadens the criteria for perturbing the initial point such that perturbation happens if the matrix is sufficiently close to being singular. As part of this, the implementation of the Newton steps has been simplified and clarified and the documentation updated, to be more clear as to exactly how the solutions to the systems of polynomial equations are computed. --- doc/tools.enumpoly.rst | 24 ++- src/core/vector.h | 3 +- src/solvers/enumpoly/efgpoly.cc | 6 +- src/solvers/enumpoly/nfgpoly.cc | 6 +- src/solvers/enumpoly/polypartial.h | 10 ++ src/solvers/enumpoly/polysolver.cc | 270 ++++++++++------------------- src/solvers/enumpoly/polysolver.h | 34 ++-- src/solvers/enumpoly/rectangle.h | 6 + 8 files changed, 143 insertions(+), 216 deletions(-) diff --git a/doc/tools.enumpoly.rst b/doc/tools.enumpoly.rst index 5921b6478..6e70cda56 100644 --- a/doc/tools.enumpoly.rst +++ b/doc/tools.enumpoly.rst @@ -8,9 +8,13 @@ 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. +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 @@ -27,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 diff --git a/src/core/vector.h b/src/core/vector.h index 57bdd43e8..4fac3e25f 100644 --- a/src/core/vector.h +++ b/src/core/vector.h @@ -158,8 +158,7 @@ template class Vector : public Array { // square of length T NormSquared() const { - return std::accumulate(this->begin(), this->end(), static_cast(0), - [](const T &t, const T &v) { return t + v * v; }); + return std::inner_product(this->begin(), this->end(), this->begin(), static_cast(0)); } }; diff --git a/src/solvers/enumpoly/efgpoly.cc b/src/solvers/enumpoly/efgpoly.cc index 5ff0dbe08..ef151f16e 100644 --- a/src/solvers/enumpoly/efgpoly.cc +++ b/src/solvers/enumpoly/efgpoly.cc @@ -165,8 +165,10 @@ std::list> SolveSupport(const BehaviorSupportProfil tops = 1; PolynomialSystemSolver solver(equations); + std::list> roots; try { - solver.FindRoots({bottoms, tops}, p_stopAfter); + roots = solver.FindRoots({bottoms, tops}, + (p_stopAfter > 0) ? p_stopAfter : std::numeric_limits::max()); } catch (const SingularMatrixException &) { p_isSingular = true; @@ -177,7 +179,7 @@ std::list> SolveSupport(const BehaviorSupportProfil } std::list> solutions; - for (auto root : solver.RootList()) { + for (auto root : roots) { MixedBehaviorProfile sol(data.sfg.ToMixedBehaviorProfile(ToSequenceProbs(data, root))); if (ExtendsToNash(sol, BehaviorSupportProfile(sol.GetGame()), BehaviorSupportProfile(sol.GetGame()))) { diff --git a/src/solvers/enumpoly/nfgpoly.cc b/src/solvers/enumpoly/nfgpoly.cc index ee6763a0d..d057afb54 100644 --- a/src/solvers/enumpoly/nfgpoly.cc +++ b/src/solvers/enumpoly/nfgpoly.cc @@ -116,8 +116,10 @@ EnumPolyStrategySupportSolve(const StrategySupportProfile &support, bool &is_sin tops = 1; PolynomialSystemSolver solver(equations); is_singular = false; + std::list> roots; try { - solver.FindRoots({bottoms, tops}, p_stopAfter); + roots = solver.FindRoots({bottoms, tops}, + (p_stopAfter > 0) ? p_stopAfter : std::numeric_limits::max()); } catch (const SingularMatrixException &) { is_singular = true; @@ -128,7 +130,7 @@ EnumPolyStrategySupportSolve(const StrategySupportProfile &support, bool &is_sin } std::list> solutions; - for (auto soln : solver.RootList()) { + for (auto soln : roots) { solutions.emplace(solutions.end(), support.NewMixedStrategyProfile()); for (auto mapping : strategy_poly) { solutions.back()[mapping.first] = mapping.second.Evaluate(soln); diff --git a/src/solvers/enumpoly/polypartial.h b/src/solvers/enumpoly/polypartial.h index 47e738b2f..d7084bd46 100644 --- a/src/solvers/enumpoly/polypartial.h +++ b/src/solvers/enumpoly/polypartial.h @@ -72,6 +72,9 @@ template class PolynomialSystemDerivatives { std::list> m_system; public: + using iterator = typename std::list>::iterator; + using const_iterator = typename std::list>::const_iterator; + explicit PolynomialSystemDerivatives(const PolynomialSystem &given) { for (const auto &p : given) { @@ -82,6 +85,13 @@ template class PolynomialSystemDerivatives { ~PolynomialSystemDerivatives() = default; PolynomialSystemDerivatives &operator=(const PolynomialSystemDerivatives &) = delete; + iterator begin() { return m_system.begin(); } + const_iterator begin() const { return m_system.begin(); } + iterator end() { return m_system.end(); } + const_iterator end() const { return m_system.end(); } + const_iterator cbegin() const { return m_system.cbegin(); } + const_iterator cend() const { return m_system.cend(); } + const PolynomialDerivatives &operator[](const int index) const { return *std::next(m_system.begin(), index - 1); diff --git a/src/solvers/enumpoly/polysolver.cc b/src/solvers/enumpoly/polysolver.cc index 9bd5624e1..af109a3e8 100644 --- a/src/solvers/enumpoly/polysolver.cc +++ b/src/solvers/enumpoly/polysolver.cc @@ -25,143 +25,87 @@ namespace Gambit { -RectArray PolynomialSystemSolver::BuildEquationVariableMatrix() const -{ - RectArray answer(m_system.size(), GetDimension()); - for (int i = 1; i <= m_system.size(); i++) { - for (int j = 1; j <= GetDimension(); j++) { - if (m_system[i].DegreeOfVar(j) > 0) { - answer(i, j) = true; - } - else { - answer(i, j) = false; - } - } - } - return answer; -} - -bool PolynomialSystemSolver::SystemHasNoRootsIn(const Rectangle &r, - Array &precedence) const -{ - for (int i = 1; i <= m_system.size(); i++) { - if ((precedence[i] <= NumEquations() && m_derivatives[precedence[i]].PolyHasNoRootsIn(r)) || - (precedence[i] > NumEquations() && - m_derivatives[precedence[i]].PolyEverywhereNegativeIn(r))) { - if (i != 1) { // We have found a new "most likely to never vanish" - int tmp = precedence[i]; - for (int j = 1; j <= i - 1; j++) { - precedence[i - j + 1] = precedence[i - j]; - } - precedence[1] = tmp; - } - return true; - } - } - return false; -} - -//-------------------------------------- -// Does Newton's method lead to a root? -//-------------------------------------- - -// -// In NewtonRootInRectangle(), problems with infinite looping will occur -// due to roundoff in trying to compare to the zero vector. -// The fuzzy_equals() functions below alleviate this by introducing -// an epsilon fudge-factor. This fudge-factor, and indeed the entire -// implementation, is largely ad-hoc, but appears to work for most -// applications. -// - namespace { -static bool fuzzy_equals(double x, double y) +bool fuzzy_equals(double x, double y) { const double epsilon = 0.000000001; if (x == 0.0) { - return fabs(y) < epsilon; + return std::abs(y) < epsilon; } if (y == 0.0) { - return fabs(x) < epsilon; + return std::abs(x) < epsilon; } - return ((fabs(x - y) / (fabs(x) + fabs(y)) < epsilon) || - (fabs(x) < epsilon && fabs(y) < epsilon)); + return ((std::abs(x - y) / (std::abs(x) + std::abs(y)) < epsilon) || + (std::abs(x) < epsilon && std::abs(y) < epsilon)); +} + +inline bool fuzzy_zero(const Vector &x) +{ + return std::all_of(x.begin(), x.end(), [](double v) { return std::abs(v) < 0.000000001; }); } } // end anonymous namespace +bool PolynomialSystemSolver::SystemHasNoRootsIn(const Rectangle &r) const +{ + return ( + std::any_of(m_derivatives.begin(), std::next(m_derivatives.begin(), NumEquations()), + [&](const PolynomialDerivatives &p) { return p.PolyHasNoRootsIn(r); }) || + std::any_of( + std::next(m_derivatives.begin(), NumEquations()), m_derivatives.end(), + [&](const PolynomialDerivatives &p) { return p.PolyEverywhereNegativeIn(r); })); +} + bool PolynomialSystemSolver::NewtonRootInRectangle(const Rectangle &r, Vector &point) const { - Vector zero(GetDimension()); - zero = 0; - - Vector oldevals = m_derivatives.ValuesOfRootPolys(point, NumEquations()); - if (std::equal(oldevals.begin(), oldevals.end(), zero.begin(), fuzzy_equals)) { + auto evals = m_derivatives.ValuesOfRootPolys(point, NumEquations()); + if (fuzzy_zero(evals)) { return r.Contains(point); } - Rectangle bigr = r.SameCenterDoubleLengths(); - Vector newpoint(GetDimension()); - while (true) { try { - newpoint = NewtonPolishOnce(point); + newpoint = NewtonStep(point); } catch (SingularMatrixException &) { - bool nonsingular = false; - int direction = 1; - while (direction < GetDimension() && !nonsingular) { + for (int i = 1; i <= point.size(); i++) { Vector perturbed_point(point); - if (r.Side(direction).UpperBound() > point[direction]) { - perturbed_point[direction] += (r.Side(direction).UpperBound() - point[direction]) / 10; + if (r.Side(i).UpperBound() > point[i]) { + perturbed_point[i] += (r.Side(i).UpperBound() - point[i]) / 10; } else { - perturbed_point[direction] += (r.Side(direction).LowerBound() - point[direction]) / 10; + perturbed_point[i] += (r.Side(i).LowerBound() - point[i]) / 10; } - nonsingular = true; - try { - newpoint = point + (NewtonPolishOnce(perturbed_point) - perturbed_point); + newpoint = point + (NewtonStep(perturbed_point) - perturbed_point); + break; } catch (SingularMatrixException &) { - nonsingular = false; } - direction++; - } - if (!nonsingular) { - Vector perturbed_point(point); - if (r.Side(direction).UpperBound() > point[direction]) { - perturbed_point[direction] += (r.Side(direction).UpperBound() - point[direction]) / 10; - } - else { - perturbed_point[direction] += (r.Side(direction).LowerBound() - point[direction]) / 10; - } - newpoint = point + (NewtonPolishOnce(perturbed_point) - perturbed_point); } } - if (!bigr.Contains(newpoint)) { + if (!r.SameCenterDoubleLengths().Contains(newpoint)) { return false; } point = newpoint; - - Vector newevals = m_derivatives.ValuesOfRootPolys(point, NumEquations()); - if (newevals * newevals >= oldevals * oldevals) { + auto newevals = m_derivatives.ValuesOfRootPolys(point, NumEquations()); + if (newevals.NormSquared() >= evals.NormSquared()) { return false; } - if (std::equal(newevals.begin(), newevals.end(), zero.begin(), fuzzy_equals)) { + if (fuzzy_zero(newevals)) { if (r.Contains(point)) { - point = SlowNewtonPolishOnce(point); - point = SlowNewtonPolishOnce(point); + point = ImprovingNewtonStep(point); + point = ImprovingNewtonStep(point); return true; } return false; } - oldevals = newevals; + evals = newevals; } } @@ -176,30 +120,25 @@ double PolynomialSystemSolver::MaxDistanceFromPointToVertexAfterTransformation( throw AssertionException( "Point not in rectangle in MaxDistanceFromPointToVertexAfterTransformation."); } - double max_length = 0.0; - Array bottom(GetDimension()), top(GetDimension()); std::fill(bottom.begin(), bottom.end(), 1); std::fill(top.begin(), top.end(), 2); + double max_length = 0.0; for (const auto &topbottoms : CartesianIndexProduct(bottom, top)) { Vector diffs(GetDimension()); for (int i = 1; i <= GetDimension(); i++) { - if (topbottoms[i] == 2) { - diffs[i] = r.Side(i).UpperBound() - p[i]; - } - else { - diffs[i] = p[i] - r.Side(i).LowerBound(); - } + diffs[i] = + (topbottoms[i] == 2) ? r.Side(i).UpperBound() - p[i] : p[i] - r.Side(i).LowerBound(); } - Vector new_diffs = M * diffs; - max_length = std::max(max_length, new_diffs * new_diffs); + max_length = std::max(max_length, (M * diffs).NormSquared()); } return std::sqrt(max_length); } -bool PolynomialSystemSolver::HasNoOtherRootsIn(const Rectangle &r, const Vector &p, - const SquareMatrix &M) const +bool PolynomialSystemSolver::HasNoOtherRootsIn(const Rectangle &r, + const Vector &p) const { + auto M = m_derivatives.SquareDerivativeMatrix(p).Inverse(); auto system2 = m_normalizedSystem.Translate(p).TransformCoords(M); double radius = MaxDistanceFromPointToVertexAfterTransformation(r, p, M); return (std::accumulate(system2.begin(), system2.end(), 0.0, @@ -208,103 +147,78 @@ bool PolynomialSystemSolver::HasNoOtherRootsIn(const Rectangle &r, const }) < radius); } -/// Does Newton's method yield a unique root? -bool PolynomialSystemSolver::NewtonRootIsOnlyInRct(const Rectangle &r, - Vector &point) const -{ - return (NewtonRootInRectangle(r, point) && - HasNoOtherRootsIn(r, point, m_derivatives.SquareDerivativeMatrix(point).Inverse())); -} - -Vector PolynomialSystemSolver::NewtonPolishOnce(const Vector &point) const +Vector PolynomialSystemSolver::NewtonStep(const Vector &point) const { - Vector oldevals = m_derivatives.ValuesOfRootPolys(point, NumEquations()); - Matrix Df = m_derivatives.DerivativeMatrix(point, NumEquations()); - return point - (Df.Transpose() * SquareMatrix(Df * Df.Transpose()).Inverse()) * oldevals; + Vector evals = m_derivatives.ValuesOfRootPolys(point, NumEquations()); + Matrix deriv = m_derivatives.DerivativeMatrix(point, NumEquations()); + auto transpose = deriv.Transpose(); + auto sqmat = SquareMatrix(deriv * transpose); + if (std::abs(sqmat.Determinant()) <= 1.0e-9) { + throw SingularMatrixException(); + } + return point - (transpose * sqmat.Inverse()) * evals; } -Vector PolynomialSystemSolver::SlowNewtonPolishOnce(const Vector &point) const +Vector PolynomialSystemSolver::ImprovingNewtonStep(const Vector &point) const { - Vector oldevals = m_derivatives.ValuesOfRootPolys(point, NumEquations()); - Matrix Df = m_derivatives.DerivativeMatrix(point, NumEquations()); - Vector Del = - -(Df.Transpose() * SquareMatrix(Df * Df.Transpose()).Inverse()) * oldevals; + Vector evals = m_derivatives.ValuesOfRootPolys(point, NumEquations()); + Matrix deriv = m_derivatives.DerivativeMatrix(point, NumEquations()); + auto transpose = deriv.Transpose(); + auto sqmat = SquareMatrix(deriv * transpose); + if (std::abs(sqmat.Determinant()) <= 1.0e-9) { + throw SingularMatrixException(); + } + auto step = -(transpose * sqmat.Inverse()) * evals; - while (true) { - Vector newevals(m_derivatives.ValuesOfRootPolys(point + Del, NumEquations())); - if (newevals * newevals <= oldevals * oldevals) { - return point + Del; - } - Del /= 2; + while (m_derivatives.ValuesOfRootPolys(point + step, NumEquations()).NormSquared() > + evals.NormSquared()) { + step /= 2; } + return point + step; } -bool PolynomialSystemSolver::FindRoots(const Rectangle &r, const int max_roots) +std::list> PolynomialSystemSolver::FindRoots(const Rectangle &r, + const int max_roots) { - const int MAX_ITER = 100000; - m_roots = List>(); - + std::list> roots; if (NumEquations() == 0) { - m_roots.push_back(Vector(0)); - return true; + roots.push_back(Vector()); } - - Array precedence(m_system.size()); - // Orders search for nonvanishing poly - std::iota(precedence.begin(), precedence.end(), 1); - - int iterations = 0; - FindRoots(m_roots, r, MAX_ITER, precedence, iterations, 1, max_roots); - return iterations < MAX_ITER; + else { + FindRoots(roots, r, max_roots); + } + return roots; } -void PolynomialSystemSolver::FindRoots(List> &rootlist, const Rectangle &r, - int max_iterations, Array &precedence, int &iterations, - int depth, int max_roots) const +void PolynomialSystemSolver::FindRoots(std::list> &rootlist, + const Rectangle &r, const int max_roots) const { - // - // TLT: In some cases, this recursive process apparently goes into an - // infinite regress. I'm not able to identify just why this occurs, - // but as at least a temporary safeguard, we will limit the maximum depth - // of this recursive search. - // - // This limit has been chosen only because it doesn't look like any - // "serious" search (i.e., one that actually terminates with a result) - // will take more than a depth of 32. - // - const int MAX_DEPTH = 32; - - if (SystemHasNoRootsIn(r, precedence)) { + if (SystemHasNoRootsIn(r)) { return; } Vector point = r.Center(); - if (NewtonRootIsOnlyInRct(r, point)) { - for (int i = NumEquations() + 1; i <= m_system.size(); i++) { - if (m_derivatives[i].ValueOfRootPoly(point) < 0.0) { - return; - } - } - - bool already_found = false; - for (size_t i = 1; i <= rootlist.size(); i++) { - if (std::equal(point.begin(), point.end(), rootlist[i].begin(), fuzzy_equals)) { - already_found = true; - } - } - if (!already_found) { + if (NewtonRootInRectangle(r, point) && HasNoOtherRootsIn(r, point)) { + // If all inequalities are satisfied and we haven't found the point before, add to the + // list of roots + if (std::all_of(std::next(m_derivatives.begin(), NumEquations()), m_derivatives.end(), + [&point](const PolynomialDerivatives &d) { + return d.ValueOfRootPoly(point) >= 0.0; + }) && + !std::any_of(rootlist.begin(), rootlist.end(), [&point](const Vector &root) { + return std::equal(point.begin(), point.end(), root.begin(), fuzzy_equals); + })) { rootlist.push_back(point); } return; } - + if (r.MaxSideLength() < 1.0e-8) { + return; + } for (const auto &cell : r.Orthants()) { - if (max_roots == 0 || rootlist.size() < max_roots) { - if (iterations >= max_iterations || depth == MAX_DEPTH) { - return; - } - iterations++; - FindRoots(rootlist, cell, max_iterations, precedence, iterations, depth + 1, max_roots); + FindRoots(rootlist, cell, max_roots); + if (rootlist.size() >= max_roots) { + return; } } } diff --git a/src/solvers/enumpoly/polysolver.h b/src/solvers/enumpoly/polysolver.h index cef4d1481..10212b452 100644 --- a/src/solvers/enumpoly/polysolver.h +++ b/src/solvers/enumpoly/polysolver.h @@ -55,41 +55,27 @@ class PolynomialSystemSolver { private: PolynomialSystem m_system, m_normalizedSystem; PolynomialSystemDerivatives m_derivatives; - List> m_roots; - RectArray eq_i_uses_j; - - // Supporting routines for the constructors - - RectArray BuildEquationVariableMatrix() const; // Check whether roots are impossible - - bool SystemHasNoRootsIn(const Rectangle &r, Array &) const; + bool SystemHasNoRootsIn(const Rectangle &r) const; // Ask whether Newton's method leads to a root - bool NewtonRootInRectangle(const Rectangle &, Vector &) const; // Ask whether we can prove that there is no root other than // the one produced by the last step - double MaxDistanceFromPointToVertexAfterTransformation(const Rectangle &, const Vector &, const SquareMatrix &) const; - bool HasNoOtherRootsIn(const Rectangle &, const Vector &, - const SquareMatrix &) const; - - // Combine the last two steps into a single query - bool NewtonRootIsOnlyInRct(const Rectangle &, Vector &) const; + bool HasNoOtherRootsIn(const Rectangle &, const Vector &) const; - void FindRoots(List> &, const Rectangle &, int, Array &, - int &iterations, int depth, int) const; + void FindRoots(std::list> &, const Rectangle &, int) const; public: explicit PolynomialSystemSolver(const PolynomialSystem &p_system) : m_system(p_system), m_normalizedSystem(p_system.Normalize()), - m_derivatives(m_normalizedSystem), eq_i_uses_j(BuildEquationVariableMatrix()) + m_derivatives(m_normalizedSystem) { } PolynomialSystemSolver(const PolynomialSystemSolver &) = delete; @@ -100,14 +86,16 @@ class PolynomialSystemSolver { // Information int GetDimension() const { return m_system.GetDimension(); } int NumEquations() const { return std::min(m_system.GetDimension(), m_system.size()); } - const List> &RootList() const { return m_roots; } - // Refines the accuracy of roots obtained from other algorithms - Vector NewtonPolishOnce(const Vector &) const; - Vector SlowNewtonPolishOnce(const Vector &) const; + /// Take a single Newton step, irrespective of whether the norm of the function at the + /// resulting point is smaller than at the original point + Vector NewtonStep(const Vector &) const; + /// Take a Newton step, sized such that the norm of the function at the resulting point + /// is smaller than at the original point + Vector ImprovingNewtonStep(const Vector &) const; // Find up to `max_roots` roots inside rectangle `r` - bool FindRoots(const Rectangle &r, int max_roots); + std::list> FindRoots(const Rectangle &r, int max_roots); }; } // end namespace Gambit diff --git a/src/solvers/enumpoly/rectangle.h b/src/solvers/enumpoly/rectangle.h index 73e3dc5a8..388449549 100644 --- a/src/solvers/enumpoly/rectangle.h +++ b/src/solvers/enumpoly/rectangle.h @@ -161,6 +161,12 @@ template class Rectangle { [](const Interval &x) { return x.Length(); }); return answer; } + T MaxSideLength() const + { + return std::accumulate( + sides.begin(), sides.end(), static_cast(0), + [](const T &v, const Interval &side) { return std::max(v, side.Length()); }); + } bool Contains(const Vector &point, const T &eps = static_cast(0)) const { return std::equal(sides.begin(), sides.end(), point.begin(), From 0319f70c929b5aae42d02ec1a52932e251d5df2d Mon Sep 17 00:00:00 2001 From: Ted Turocy Date: Mon, 13 Jan 2025 13:15:00 +0000 Subject: [PATCH 55/99] Update version to 16.3.0. --- ChangeLog | 2 +- configure.ac | 2 +- contrib/mac/Info.plist | 8 ++++---- doc/conf.py | 4 ++-- doc/index.rst | 2 +- doc/pygambit.user.rst | 10 +++++----- doc/tools.convert.rst | 4 ++-- doc/tools.enummixed.rst | 8 ++------ doc/tools.enumpoly.rst | 2 +- doc/tools.enumpure.rst | 6 +++--- doc/tools.gnm.rst | 2 +- doc/tools.ipa.rst | 2 +- doc/tools.lcp.rst | 2 +- doc/tools.liap.rst | 2 +- doc/tools.logit.rst | 2 +- doc/tools.lp.rst | 2 +- doc/tools.simpdiv.rst | 2 +- gambit.wxs | 2 +- setup.py | 4 ++-- src/pygambit/__init__.py | 2 +- src/pygambit/game.pxi | 2 +- 21 files changed, 34 insertions(+), 38 deletions(-) diff --git a/ChangeLog b/ChangeLog index ec171ec20..5939a7819 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,6 +1,6 @@ # Changelog -## [16.3.0] - unreleased +## [16.3.0] - 2025-01-13 ### General - Dropped support for Python 3.8. diff --git a/configure.ac b/configure.ac index fa7174db7..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]) 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/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/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/pygambit.user.rst b/doc/pygambit.user.rst index 34a2e196d..26a46cc98 100644 --- a/doc/pygambit.user.rst +++ b/doc/pygambit.user.rst @@ -315,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: @@ -325,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 @@ -363,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 @@ -562,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 @@ -646,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, 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 6e70cda56..790ae9adb 100644 --- a/doc/tools.enumpoly.rst +++ b/doc/tools.enumpoly.rst @@ -98,7 +98,7 @@ Computing equilibria of the strategic game :download:`e01.nfg $ gambit-enumpoly e01.nfg Compute Nash equilibria by solving polynomial systems - 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,1.000000,0.000000,0.000000,1.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 c09c6e8e6..68a9bf0fc 100644 --- a/doc/tools.lcp.rst +++ b/doc/tools.lcp.rst @@ -79,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 aed3affdc..04dc0e36a 100644 --- a/doc/tools.logit.rst +++ b/doc/tools.logit.rst @@ -108,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.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/setup.py b/setup.py index 019940e71..cd542da4d 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 @@ -125,7 +125,7 @@ def readme(): license="GPL2+", author="Theodore Turocy", author_email="ted.turocy@gmail.com", - url="http://www.gambit-project.org", + url="https://www.gambit-project.org", project_urls={ "Documentation": "https://gambitproject.readthedocs.io/", "Source": "https://github.com/gambitproject/gambit", diff --git a/src/pygambit/__init__.py b/src/pygambit/__init__.py index 4b90426ed..8d399dfa9 100644 --- a/src/pygambit/__init__.py +++ b/src/pygambit/__init__.py @@ -28,4 +28,4 @@ supports, # noqa: F401 ) -__version__ = "16.2.1" +__version__ = "16.3.0" diff --git a/src/pygambit/game.pxi b/src/pygambit/game.pxi index 371cf6f98..b75f201dd 100644 --- a/src/pygambit/game.pxi +++ b/src/pygambit/game.pxi @@ -651,7 +651,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 ( From 479c8141d46d396109cdde3fcc81ac9c48f8f24c Mon Sep 17 00:00:00 2001 From: Ted Turocy Date: Tue, 14 Jan 2025 15:52:18 +0000 Subject: [PATCH 56/99] This makes a few updates/modernisations to the Cython implementation * Use templates to declare profile types. Templates were not supported yet in Cython when these were originally implemented. * Remove a few unnecessary helper functions * Use shared_ptr to implement StrategySupportProfile rather than unique_ptr, to align with the way other profile types are implemented. --- src/pygambit/behavmixed.pxi | 22 ++-- src/pygambit/gambit.pxd | 243 ++++++++++++++---------------------- src/pygambit/gambit.pyx | 2 +- src/pygambit/game.pxi | 30 +++-- src/pygambit/nash.pxi | 35 +++--- src/pygambit/stratmixed.pxi | 22 ++-- src/pygambit/stratspt.pxi | 40 +++--- src/pygambit/util.h | 24 +--- 8 files changed, 176 insertions(+), 242 deletions(-) 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 96cd23062..e1d33e3d4 100644 --- a/src/pygambit/gambit.pxd +++ b/src/pygambit/gambit.pxd @@ -13,8 +13,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": @@ -35,6 +35,7 @@ cdef extern from "core/array.h": int size() except + Array() except + Array(int) except + + void push_back(T) except + iterator begin() except + iterator end() except + @@ -225,13 +226,10 @@ cdef extern from "games/game.h": 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(Array[int]) except + cdef extern from "games/stratpure.h": @@ -245,98 +243,52 @@ 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 + - 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 + +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_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": @@ -365,9 +317,9 @@ cdef extern from "games/stratspt.h": 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 + @@ -389,44 +341,39 @@ cdef extern from "util.h": string WriteNfgFile(c_Game) string WriteLaTeXFile(c_Game) string WriteHTMLFile(c_Game) - # string WriteGbtFile(c_Game) - c_Rational to_rational(char *) except + - - stdlist[c_StrategySupportProfile *] make_list_of_pointer( - stdlist[c_StrategySupportProfile] - ) except + + 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 @@ -437,60 +384,64 @@ cdef extern from "util.h": 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_List[c_MixedStrategyProfile[double]] EnumMixedStrategySolveDouble( + c_Game + ) except +RuntimeError + c_List[c_MixedStrategyProfile[c_Rational]] EnumMixedStrategySolveRational( + c_Game + ) except +RuntimeError + c_List[c_MixedStrategyProfile[c_Rational]] EnumMixedStrategySolveLrs( c_Game ) except +RuntimeError - c_List[c_MixedStrategyProfileRational] EnumMixedStrategySolveLrs(c_Game) except +RuntimeError cdef extern from "solvers/lcp/lcp.h": - c_List[c_MixedStrategyProfileDouble] LcpStrategySolveDouble( + c_List[c_MixedStrategyProfile[double]] LcpStrategySolveDouble( c_Game, int p_stopAfter, int p_maxDepth ) except +RuntimeError - c_List[c_MixedStrategyProfileRational] LcpStrategySolveRational( + c_List[c_MixedStrategyProfile[c_Rational]] LcpStrategySolveRational( c_Game, int p_stopAfter, int p_maxDepth ) except +RuntimeError - c_List[c_MixedBehaviorProfileDouble] LcpBehaviorSolveDouble( + c_List[c_MixedBehaviorProfile[double]] LcpBehaviorSolveDouble( c_Game, int p_stopAfter, int p_maxDepth ) except +RuntimeError - c_List[c_MixedBehaviorProfileRational] LcpBehaviorSolveRational( + c_List[c_MixedBehaviorProfile[c_Rational]] LcpBehaviorSolveRational( c_Game, int p_stopAfter, int p_maxDepth ) except +RuntimeError cdef extern from "solvers/lp/nfglp.h": - c_List[c_MixedStrategyProfileDouble] LpStrategySolveDouble(c_Game) except +RuntimeError - c_List[c_MixedStrategyProfileRational] LpStrategySolveRational(c_Game) except +RuntimeError + c_List[c_MixedStrategyProfile[double]] LpStrategySolveDouble(c_Game) except +RuntimeError + c_List[c_MixedStrategyProfile[c_Rational]] LpStrategySolveRational(c_Game) except +RuntimeError cdef extern from "solvers/lp/efglp.h": - c_List[c_MixedBehaviorProfileDouble] LpBehaviorSolveDouble(c_Game) except +RuntimeError - c_List[c_MixedBehaviorProfileRational] LpBehaviorSolveRational(c_Game) except +RuntimeError + c_List[c_MixedBehaviorProfile[double]] LpBehaviorSolveDouble(c_Game) except +RuntimeError + c_List[c_MixedBehaviorProfile[c_Rational]] LpBehaviorSolveRational(c_Game) except +RuntimeError cdef extern from "solvers/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 @@ -502,10 +453,10 @@ cdef extern from "solvers/nashsupport/nashsupport.h": ) except +RuntimeError cdef extern from "solvers/enumpoly/enumpoly.h": - c_List[c_MixedStrategyProfileDouble] EnumPolyStrategySolve( + c_List[c_MixedStrategyProfile[double]] EnumPolyStrategySolve( c_Game, int, float ) except +RuntimeError - c_List[c_MixedBehaviorProfileDouble] EnumPolyBehaviorSolve( + c_List[c_MixedBehaviorProfile[double]] EnumPolyBehaviorSolve( c_Game, int, float ) except +RuntimeError @@ -514,7 +465,7 @@ cdef extern from "solvers/logit/logit.h": c_LogitQREMixedBehaviorProfile(c_Game) except + c_LogitQREMixedBehaviorProfile(c_LogitQREMixedBehaviorProfile) except + c_Game GetGame() except + - c_MixedBehaviorProfileDouble GetProfile() # except + doesn't compile + c_MixedBehaviorProfile[double] GetProfile() # except + doesn't compile double GetLambda() except + double GetLogLike() except + int size() except + @@ -524,7 +475,7 @@ cdef extern from "solvers/logit/logit.h": 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 size() except + @@ -532,7 +483,7 @@ cdef extern from "solvers/logit/logit.h": cdef extern from "nash.h": - c_List[c_MixedBehaviorProfileDouble] LogitBehaviorSolveWrapper( + c_List[c_MixedBehaviorProfile[double]] LogitBehaviorSolveWrapper( c_Game, double, double, double ) except + c_List[c_LogitQREMixedBehaviorProfile] LogitBehaviorPrincipalBranchWrapper( @@ -542,9 +493,9 @@ cdef extern from "nash.h": c_Game, stdlist[double], double, double ) except + shared_ptr[c_LogitQREMixedBehaviorProfile] LogitBehaviorEstimateWrapper( - shared_ptr[c_MixedBehaviorProfileDouble], bool, double, double + shared_ptr[c_MixedBehaviorProfile[double]], bool, double, double ) except + - c_List[c_MixedStrategyProfileDouble] LogitStrategySolveWrapper( + c_List[c_MixedStrategyProfile[double]] LogitStrategySolveWrapper( c_Game, double, double, double ) except + c_List[c_LogitQREMixedStrategyProfile] LogitStrategyPrincipalBranchWrapper( @@ -554,5 +505,5 @@ cdef extern from "nash.h": c_Game, stdlist[double], double, double ) except + shared_ptr[c_LogitQREMixedStrategyProfile] LogitStrategyEstimateWrapper( - shared_ptr[c_MixedStrategyProfileDouble], bool, double, double + 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 b75f201dd..57296e9c3 100644 --- a/src/pygambit/game.pxi +++ b/src/pygambit/game.pxi @@ -446,14 +446,10 @@ 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 + cdef Array[int] d + for v in dim: + d.push_back(v) + g = Game.wrap(NewTable(d)) g.title = title return g @@ -919,12 +915,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) @@ -1038,12 +1036,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) @@ -1122,11 +1120,11 @@ class Game: ------- StrategySupportProfile """ - profile = StrategySupportProfile.wrap(self) + 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.support) + if not (deref(profile.profile) .RemoveStrategy(cython.cast(Strategy, strategy).strategy)): raise ValueError("attempted to remove the last strategy for player") return profile diff --git a/src/pygambit/nash.pxi b/src/pygambit/nash.pxi index 46125a877..f45403e42 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.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.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.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.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)) @@ -163,13 +163,12 @@ def _gnm_strategy_solve( def _nashsupport_strategy_solve(game: Game) -> typing.List[StrategySupportProfile]: - ret = [] - result = PossibleNashStrategySupports(game.game) - for c_support in make_list_of_pointer(deref(result).m_supports): - support = StrategySupportProfile(game, list(game.strategies)) - support.support.reset(c_support) - ret.append(support) - return ret + return [ + StrategySupportProfile.wrap(support) + for support in make_list_of_pointer( + deref(PossibleNashStrategySupports(game.game)).m_supports + ) + ] def _enumpoly_strategy_solve( @@ -244,7 +243,7 @@ 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()) ) @@ -325,7 +324,7 @@ class LogitQREMixedBehaviorProfile: """The mixed strategy profile.""" profile = MixedBehaviorProfileDouble() profile.profile = ( - make_shared[c_MixedBehaviorProfileDouble](deref(self.thisptr).GetProfile()) + make_shared[c_MixedBehaviorProfile[double]](deref(self.thisptr).GetProfile()) ) return profile 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 735145c6a..696177dbc 100644 --- a/src/pygambit/stratspt.pxi +++ b/src/pygambit/stratspt.pxi @@ -40,10 +40,8 @@ class StrategySupport: return self._player def __iter__(self) -> typing.Generator[Strategy, None, None]: - for strat in deref(self._profile.support).GetStrategies(self._player.player): - s = Strategy() - s.strategy = strat - yield s + for strat in deref(self._profile.profile).GetStrategies(self._player.player): + yield Strategy.wrap(strat) @cython.cclass @@ -52,34 +50,34 @@ class StrategySupportProfile: 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) -> StrategySupportProfile: + def wrap(profile: shared_ptr[c_StrategySupportProfile]) -> StrategySupportProfile: obj: StrategySupportProfile = StrategySupportProfile.__new__(StrategySupportProfile) - obj.support.reset(new c_StrategySupportProfile(game.game)) + 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: @@ -93,11 +91,11 @@ class StrategySupportProfile: 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 player in deref(self.support).GetGame().deref().GetPlayers(): - for strat in deref(self.support).GetStrategies(player): + 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: @@ -163,7 +161,7 @@ class StrategySupportProfile: raise MismatchError( "remove(): strategy is not part of the game on which the profile is defined." ) - if deref(self.support).GetStrategies( + if deref(self.profile).GetStrategies( cython.cast(Player, strategy.player).player ).size() == 1: raise UndefinedOperationError( @@ -262,7 +260,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. @@ -294,17 +292,17 @@ 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")) + return Game.parse_game(WriteGame(deref(self.profile)).decode("ascii")) def is_dominated(self, strategy: Strategy, strict: bool, external: bool = False) -> bool: - return deref(self.support).IsDominated(strategy.strategy, strict, external) + 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/util.h b/src/pygambit/util.h index cfe0b203e..bfd2eac52 100644 --- a/src/pygambit/util.h +++ b/src/pygambit/util.h @@ -36,8 +36,6 @@ using namespace std; using namespace Gambit; using namespace Gambit::Nash; -inline Game NewTable(Array *dim) { return NewTable(*dim); } - Game ReadGame(char *fn) { std::ifstream f(fn); @@ -124,14 +122,6 @@ std::string WriteGame(const StrategySupportProfile &p_support) return f.str(); } -// Create a copy on the heap (via new) of the element at index p_index of -// container p_container. -template