From 0c2d2f6e1dd57f7f3673d5bbbc1de8c8d569a161 Mon Sep 17 00:00:00 2001 From: Ted Turocy Date: Tue, 26 Mar 2024 14:21:50 +0000 Subject: [PATCH] Change logit numerical continuation to terminate on regret-based criterion. This changes the numerical continuation in logit to terminate when it reaches a profile which has less than a specified level of regret (epsilon), which is interpreted relative to the scale of payoffs of the game. Closes #365. --- doc/tools.logit.rst | 18 +++++----- src/pygambit/gambit.pxd | 4 +-- src/pygambit/nash.h | 5 ++- src/pygambit/nash.pxi | 13 +++---- src/pygambit/nash.py | 18 ++++++++-- src/solvers/logit/efglogit.cc | 64 +++++++++++++++++++++++++++++------ src/solvers/logit/efglogit.h | 11 +++--- src/solvers/logit/nfglogit.cc | 55 ++++++++++++++++++++++-------- src/solvers/logit/nfglogit.h | 7 ++-- src/solvers/logit/path.cc | 7 ++-- src/solvers/logit/path.h | 21 ++++++++++-- src/tools/liap/liap.cc | 6 ++-- src/tools/logit/logit.cc | 15 +++++--- tests/test_nash.py | 5 ++- 14 files changed, 180 insertions(+), 69 deletions(-) diff --git a/doc/tools.logit.rst b/doc/tools.logit.rst index 505e401f5..7f4d8d6c9 100644 --- a/doc/tools.logit.rst +++ b/doc/tools.logit.rst @@ -33,6 +33,13 @@ if an information set is not reached due to being the successor of chance moves with zero probability. In such games, the implementation treats the beliefs at such information sets as being uniform across all member nodes. +.. versionchanged:: 16.2.0 + + The criterion for accepting whether a point is sufficiently close to a + Nash equilibrium to terminate the path-following is specified + in terms of the maximum regret. This regret is interpreted as a fraction + of the difference between the maximum and minimum payoffs in the game. + .. program:: gambit-logit .. cmdoption:: -d @@ -61,14 +68,9 @@ the beliefs at such information sets as being uniform across all member nodes. .. cmdoption:: -m - Stop when reaching the specified value of the - parameter lambda. By default, the tracing stops when lambda reaches - 1,000,000, which is usually suitable for computing a good - approximation to a Nash equilibrium. For applications, such as to - laboratory experiments, where the behavior of the correspondence for - small values of lambda is of interest and the asymptotic behavior is - not relevant, setting MAXLAMBDA to a much smaller value may be - indicated. + .. versionadded:: 16.2.0 + + Specify the maximum regret criterion for acceptance as an approximate Nash equilibrium. .. cmdoption:: -l diff --git a/src/pygambit/gambit.pxd b/src/pygambit/gambit.pxd index 9670b7e2b..29a04bf93 100644 --- a/src/pygambit/gambit.pxd +++ b/src/pygambit/gambit.pxd @@ -455,10 +455,10 @@ cdef extern from "solvers/gnm/gnm.h": ) except +RuntimeError cdef extern from "solvers/logit/nfglogit.h": - c_List[c_MixedStrategyProfileDouble] LogitStrategySolve(c_Game) except +RuntimeError + c_List[c_MixedStrategyProfileDouble] LogitStrategySolve(c_Game, double) except +RuntimeError cdef extern from "solvers/logit/efglogit.h": - c_List[c_MixedBehaviorProfileDouble] LogitBehaviorSolve(c_Game) except +RuntimeError + c_List[c_MixedBehaviorProfileDouble] LogitBehaviorSolve(c_Game, double) except +RuntimeError cdef extern from "solvers/logit/nfglogit.h": cdef cppclass c_LogitQREMixedStrategyProfile "LogitQREMixedStrategyProfile": diff --git a/src/pygambit/nash.h b/src/pygambit/nash.h index c6ad0f2f5..d4594926d 100644 --- a/src/pygambit/nash.h +++ b/src/pygambit/nash.h @@ -53,12 +53,11 @@ std::shared_ptr logit_atlambda(const Game &p_game, alg.SolveAtLambda(start, null_stream, p_lambda, 1.0)); } -List logit_principal_branch(const Game &p_game, - double p_maxLambda = 1000000.0) +List logit_principal_branch(const Game &p_game, double p_maxregret) { LogitQREMixedStrategyProfile start(p_game); StrategicQREPathTracer alg; NullBuffer null_buffer; std::ostream null_stream(&null_buffer); - return alg.TraceStrategicPath(start, null_stream, p_maxLambda, 1.0); + return alg.TraceStrategicPath(start, null_stream, p_maxregret, 1.0); } diff --git a/src/pygambit/nash.pxi b/src/pygambit/nash.pxi index 6ebe3b6fc..7e4d46b5d 100644 --- a/src/pygambit/nash.pxi +++ b/src/pygambit/nash.pxi @@ -33,6 +33,7 @@ def _convert_mspd( ) -> typing.List[MixedStrategyProfileDouble]: ret = [] for i in range(inlist.Length()): + print(i) p = MixedStrategyProfileDouble() p.profile = copyitem_list_mspd(inlist, i+1) ret.append(p) @@ -182,12 +183,12 @@ def _gnm_strategy_solve( raise -def _logit_strategy_solve(game: Game) -> typing.List[MixedStrategyProfileDouble]: - return _convert_mspd(LogitStrategySolve(game.game)) +def _logit_strategy_solve(game: Game, maxregret: float) -> typing.List[MixedStrategyProfileDouble]: + return _convert_mspd(LogitStrategySolve(game.game, maxregret)) -def _logit_behavior_solve(game: Game) -> typing.List[MixedBehaviorProfileDouble]: - return _convert_mbpd(LogitBehaviorSolve(game.game)) +def _logit_behavior_solve(game: Game, maxregret: float) -> typing.List[MixedBehaviorProfileDouble]: + return _convert_mbpd(LogitBehaviorSolve(game.game, maxregret)) @cython.cclass @@ -254,8 +255,8 @@ def logit_atlambda(game: Game, lam: float) -> LogitQREMixedStrategyProfile: return ret -def logit_principal_branch(game: Game, maxlam: float = 100000.0): - solns = _logit_principal_branch(game.game, maxlam) +def logit_principal_branch(game: Game): + solns = _logit_principal_branch(game.game, 1.0e-6) ret = [] for i in range(solns.Length()): p = LogitQREMixedStrategyProfile() diff --git a/src/pygambit/nash.py b/src/pygambit/nash.py index cea15d41b..bee449975 100644 --- a/src/pygambit/nash.py +++ b/src/pygambit/nash.py @@ -516,7 +516,9 @@ def gnm_solve( def logit_solve( - game: libgbt.Game, use_strategic: bool = False + game: libgbt.Game, + use_strategic: bool = False, + maxregret: float = 0.0001, ) -> NashComputationResult: """Compute Nash equilibria of a game using :ref:`the logit quantal response equilibrium correspondence `. @@ -528,19 +530,29 @@ def logit_solve( ---------- 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. + maxregret : float, default 0.0001 + 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 + + .. versionadded: 16.2.0 + Returns ------- res : NashComputationResult The result represented as a ``NashComputationResult`` object. """ + if maxregret <= 0.0: + raise ValueError("logit_solve(): maxregret argument must be positive") if not game.is_tree or use_strategic: - equilibria = libgbt._logit_strategy_solve(game) + equilibria = libgbt._logit_strategy_solve(game, maxregret) else: - equilibria = libgbt._logit_behavior_solve(game) + equilibria = libgbt._logit_behavior_solve(game, maxregret) return NashComputationResult( game=game, method="logit", diff --git a/src/solvers/logit/efglogit.cc b/src/solvers/logit/efglogit.cc index 6cf117a35..4ba247621 100644 --- a/src/solvers/logit/efglogit.cc +++ b/src/solvers/logit/efglogit.cc @@ -36,9 +36,12 @@ namespace Gambit { class AgentQREPathTracer::EquationSystem : public PathTracer::EquationSystem { public: explicit EquationSystem(const Game &p_game); + ~EquationSystem() override; + // 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; @@ -49,7 +52,9 @@ class AgentQREPathTracer::EquationSystem : public PathTracer::EquationSystem { class Equation { public: virtual ~Equation() = default; + virtual double Value(const LogBehavProfile &p_point, double p_lambda) const = 0; + virtual void Gradient(const LogBehavProfile &p_point, double p_lambda, Vector &p_gradient) const = 0; }; @@ -72,6 +77,7 @@ class AgentQREPathTracer::EquationSystem : public PathTracer::EquationSystem { } double Value(const LogBehavProfile &p_profile, double p_lambda) const override; + void Gradient(const LogBehavProfile &p_profile, double p_lambda, Vector &p_gradient) const override; }; @@ -95,6 +101,7 @@ class AgentQREPathTracer::EquationSystem : public PathTracer::EquationSystem { } double Value(const LogBehavProfile &p_profile, double p_lambda) const override; + void Gradient(const LogBehavProfile &p_profile, double p_lambda, Vector &p_gradient) const override; }; @@ -238,9 +245,11 @@ class AgentQREPathTracer::CallbackFunction : public PathTracer::CallbackFunction : m_stream(p_stream), m_game(p_game), m_fullGraph(p_fullGraph), m_decimals(p_decimals) { } + ~CallbackFunction() override = default; void operator()(const Vector &p_point, bool p_isTerminal) const override; + const List &GetProfiles() const { return m_profiles; } private: @@ -302,27 +311,62 @@ class AgentQREPathTracer::LambdaCriterion : public PathTracer::CriterionFunction // AgentQREPathTracer: Wrapper to the tracing engine //------------------------------------------------------------------------------ +namespace { + +bool RegretTerminationFunction(const Game &p_game, const Vector &p_point, double p_regret) +{ + if (p_point.back() < 0.0) { + return false; + } + MixedBehaviorProfile profile(p_game); + for (int i = 1; i < p_point.Length(); i++) { + profile[i] = exp(p_point[i]); + } + return profile.GetMaxRegret() < p_regret; +} + +} // namespace + List AgentQREPathTracer::TraceAgentPath(const LogitQREMixedBehaviorProfile &p_start, - std::ostream &p_stream, double p_maxLambda, double p_omega, - double p_targetLambda) + std::ostream &p_stream, double p_regret, double p_omega) const { + 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[x.Length()] = p_start.GetLambda(); + x.back() = p_start.GetLambda(); CallbackFunction func(p_stream, p_start.GetGame(), m_fullGraph, m_decimals); - if (p_targetLambda > 0.0) { - TracePath(EquationSystem(p_start.GetGame()), x, p_maxLambda, p_omega, func, - LambdaCriterion(p_targetLambda)); - } - else { - TracePath(EquationSystem(p_start.GetGame()), x, p_maxLambda, p_omega, func); - } + 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); + }, + func); 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(); +} + } // end namespace Gambit diff --git a/src/solvers/logit/efglogit.h b/src/solvers/logit/efglogit.h index 51c2524c5..85614b536 100644 --- a/src/solvers/logit/efglogit.h +++ b/src/solvers/logit/efglogit.h @@ -53,8 +53,11 @@ class AgentQREPathTracer : public PathTracer { ~AgentQREPathTracer() override = default; List TraceAgentPath(const LogitQREMixedBehaviorProfile &p_start, - std::ostream &p_stream, double p_maxLambda, - double p_omega, double p_targetLambda = -1.0); + 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; } @@ -71,13 +74,13 @@ class AgentQREPathTracer : public PathTracer { class LambdaCriterion; }; -inline List> LogitBehaviorSolve(const Game &p_game) +inline List> LogitBehaviorSolve(const Game &p_game, double p_epsilon) { AgentQREPathTracer tracer; tracer.SetFullGraph(false); std::ostringstream ostream; auto result = - tracer.TraceAgentPath(LogitQREMixedBehaviorProfile(p_game), ostream, 1000000.0, 1.0); + tracer.TraceAgentPath(LogitQREMixedBehaviorProfile(p_game), ostream, p_epsilon, 1.0); auto ret = List>(); ret.push_back(result[1].GetProfile()); return ret; diff --git a/src/solvers/logit/nfglogit.cc b/src/solvers/logit/nfglogit.cc index 30b29d8be..622fc4148 100644 --- a/src/solvers/logit/nfglogit.cc +++ b/src/solvers/logit/nfglogit.cc @@ -209,18 +209,44 @@ void StrategicQREPathTracer::CallbackFunction::operator()(const Vector & // StrategicQREPathTracer: Main driver routines //---------------------------------------------------------------------------- +namespace { + +bool RegretTerminationFunction(const Game &p_game, const Vector &p_point, double p_regret) +{ + if (p_point.back() < 0.0) { + return false; + } + MixedStrategyProfile profile(p_game->NewMixedStrategyProfile(0.0)); + for (int i = 1; i < p_point.Length(); i++) { + profile[i] = exp(p_point[i]); + } + return profile.GetMaxRegret() < p_regret; +} + +} // namespace + List StrategicQREPathTracer::TraceStrategicPath(const LogitQREMixedStrategyProfile &p_start, - std::ostream &p_stream, double p_maxLambda, + std::ostream &p_stream, double p_regret, double p_omega) const { + 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[x.Length()] = p_start.GetLambda(); + x.back() = p_start.GetLambda(); CallbackFunction func(p_stream, p_start.GetGame(), m_fullGraph, m_decimals); - TracePath(EquationSystem(p_start.GetGame()), x, p_maxLambda, p_omega, func); + 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); + }, + func); return func.GetProfiles(); } @@ -233,10 +259,10 @@ StrategicQREPathTracer::SolveAtLambda(const LogitQREMixedStrategyProfile &p_star for (int i = 1; i <= p_start.MixedProfileLength(); i++) { x[i] = log(p_start[i]); } - x[x.Length()] = p_start.GetLambda(); + x.back() = p_start.GetLambda(); CallbackFunction func(p_stream, p_start.GetGame(), m_fullGraph, m_decimals); - TracePath(EquationSystem(p_start.GetGame()), x, std::max(1.0, 3.0 * p_targetLambda), p_omega, - func, LambdaCriterion(p_targetLambda)); + TracePath(EquationSystem(p_start.GetGame()), x, p_omega, LambdaPositiveTerminationFunction, func, + LambdaCriterion(p_targetLambda)); return func.GetProfiles().back(); } @@ -380,19 +406,18 @@ StrategicQREEstimator::Estimate(const LogitQREMixedStrategyProfile &p_start, for (int i = 1; i <= p_start.MixedProfileLength(); i++) { x[i] = log(p_start[i]); } - x[x.Length()] = p_start.GetLambda(); + x.back() = p_start.GetLambda(); CallbackFunction callback(p_stream, p_start.GetGame(), static_cast &>(p_frequencies), m_fullGraph, m_decimals); - while (x[x.Length()] < p_maxLambda) { - TracePath(EquationSystem(p_start.GetGame()), x, p_maxLambda, p_omega, callback, - CriterionFunction(static_cast &>(p_frequencies))); - if (x[x.Length()] < p_maxLambda) { - // Found an extremum of the likelihood function - // start iterating again from the same point in case of - // local optima. - } + 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(); diff --git a/src/solvers/logit/nfglogit.h b/src/solvers/logit/nfglogit.h index ab1ebca16..42240348b 100644 --- a/src/solvers/logit/nfglogit.h +++ b/src/solvers/logit/nfglogit.h @@ -68,7 +68,7 @@ class StrategicQREPathTracer : public PathTracer { List TraceStrategicPath(const LogitQREMixedStrategyProfile &p_start, std::ostream &p_logStream, - double p_maxLambda, double p_omega) const; + double p_maxregret, double p_omega) const; LogitQREMixedStrategyProfile SolveAtLambda(const LogitQREMixedStrategyProfile &p_start, std::ostream &p_logStream, double p_targetLambda, double p_omega) const; @@ -103,13 +103,14 @@ class StrategicQREEstimator : public StrategicQREPathTracer { class CallbackFunction; }; -inline List> LogitStrategySolve(const Game &p_game) +inline List> LogitStrategySolve(const Game &p_game, + double p_regret = 0.0001) { StrategicQREPathTracer tracer; tracer.SetFullGraph(false); std::ostringstream ostream; auto result = - tracer.TraceStrategicPath(LogitQREMixedStrategyProfile(p_game), ostream, 1000000.0, 1.0); + tracer.TraceStrategicPath(LogitQREMixedStrategyProfile(p_game), ostream, p_regret, 1.0); auto ret = List>(); ret.push_back(result[1].GetProfile()); return ret; diff --git a/src/solvers/logit/path.cc b/src/solvers/logit/path.cc index d79eeb8c0..0fe11bbdd 100644 --- a/src/solvers/logit/path.cc +++ b/src/solvers/logit/path.cc @@ -109,8 +109,8 @@ void NewtonStep(Matrix &q, Matrix &b, Vector &u, Vector< // PathTracer: Implementation of path-following engine //---------------------------------------------------------------------------- -void PathTracer::TracePath(const EquationSystem &p_system, Vector &x, double p_maxLambda, - double &p_omega, const CallbackFunction &p_callback, +void PathTracer::TracePath(const EquationSystem &p_system, Vector &x, double &p_omega, + TerminationFunctionType p_terminate, const CallbackFunction &p_callback, const CriterionFunction &p_criterion) const { const double c_tol = 1.0e-4; // tolerance for corrector iteration @@ -136,7 +136,7 @@ void PathTracer::TracePath(const EquationSystem &p_system, Vector &x, do QRDecomp(b, q); q.GetRow(q.NumRows(), t); - while (x[x.Length()] >= 0.0 && x[x.Length()] < p_maxLambda) { + while (!p_terminate(x)) { bool accept = true; if (fabs(h) <= c_hmin) { @@ -250,6 +250,7 @@ void PathTracer::TracePath(const EquationSystem &p_system, Vector &x, do t = newT; } + // 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 31b1d6351..8c0b7063f 100644 --- a/src/solvers/logit/path.h +++ b/src/solvers/logit/path.h @@ -23,8 +23,24 @@ #ifndef PATH_H #define PATH_H +#include + namespace Gambit { +// Function type used for determining whether to terminate the numerical continuation +using TerminationFunctionType = std::function &)>; + +inline bool LambdaPositiveTerminationFunction(const Vector &p_point) +{ + return (p_point.back() >= 0.0); +} + +inline bool LambdaRangeTerminationFunction(const Vector &p_point, double p_minLambda, + double p_maxLambda) +{ + return (p_point.back() >= p_minLambda && p_point.back() <= p_maxLambda); +} + // // 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 @@ -94,8 +110,9 @@ class PathTracer { PathTracer() : m_maxDecel(1.1), m_hStart(0.03) {} virtual ~PathTracer() = default; - void TracePath(const EquationSystem &p_system, Vector &p_x, double p_maxLambda, - double &p_omega, const CallbackFunction &p_callback = NullCallbackFunction(), + 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; private: diff --git a/src/tools/liap/liap.cc b/src/tools/liap/liap.cc index 3df17348c..2cb4a2a5e 100644 --- a/src/tools/liap/liap.cc +++ b/src/tools/liap/liap.cc @@ -142,7 +142,7 @@ int main(int argc, char *argv[]) {"verbose", 0, nullptr, 'V'}, {nullptr, 0, nullptr, 0}}; int c; - while ((c = getopt_long(argc, argv, "d:n:i:s:hqVvS", long_options, &long_opt_index)) != -1) { + while ((c = getopt_long(argc, argv, "d:n:i:s:m:hqVvS", long_options, &long_opt_index)) != -1) { switch (c) { case 'v': PrintBanner(std::cerr); @@ -150,8 +150,8 @@ int main(int argc, char *argv[]) case 'd': numDecimals = atoi(optarg); break; - case 'n': - numTries = atoi(optarg); + case 'm': + maxregret = atof(optarg); break; case 'i': maxitsN = atoi(optarg); diff --git a/src/tools/logit/logit.cc b/src/tools/logit/logit.cc index d22fea19c..7771641a5 100644 --- a/src/tools/logit/logit.cc +++ b/src/tools/logit/logit.cc @@ -49,7 +49,8 @@ void PrintHelp(char *progname) std::cerr << " -d DECIMALS show equilibria as floating point with DECIMALS digits\n"; std::cerr << " -s STEP initial stepsize (default is .03)\n"; std::cerr << " -a ACCEL maximum acceleration (default is 1.1)\n"; - std::cerr << " -m MAXLAMBDA stop when reaching MAXLAMBDA (default is 1000000)\n"; + std::cerr << " -m MAXREGRET maximum regret acceptable as a proportion of range of\n"; + std::cerr << " payoffs in the game\n"; std::cerr << " -l LAMBDA compute QRE at `lambda` accurately\n"; std::cerr << " -L FILE compute maximum likelihood estimates;\n"; std::cerr << " read strategy frequencies from FILE\n"; @@ -89,6 +90,7 @@ int main(int argc, char *argv[]) bool quiet = false, useStrategic = false; double maxLambda = 1000000.0; + double maxregret = 0.0001; std::string mleFile; double maxDecel = 1.1; double hStart = 0.03; @@ -119,7 +121,7 @@ int main(int argc, char *argv[]) maxDecel = atof(optarg); break; case 'm': - maxLambda = atof(optarg); + maxregret = atof(optarg); break; case 'e': fullGraph = false; @@ -199,7 +201,7 @@ int main(int argc, char *argv[]) tracer.SolveAtLambda(start, std::cout, targetLambda, 1.0); } else { - tracer.TraceStrategicPath(start, std::cout, maxLambda, 1.0); + tracer.TraceStrategicPath(start, std::cout, maxregret, 1.0); } } else { @@ -209,7 +211,12 @@ int main(int argc, char *argv[]) tracer.SetStepsize(hStart); tracer.SetFullGraph(fullGraph); tracer.SetDecimals(decimals); - tracer.TraceAgentPath(start, std::cout, maxLambda, 1.0, targetLambda); + if (targetLambda > 0.0) { + tracer.SolveAtLambda(start, std::cout, targetLambda, 1.0); + } + else { + tracer.TraceAgentPath(start, std::cout, maxregret, 1.0); + } } return 0; } diff --git a/tests/test_nash.py b/tests/test_nash.py index aa88ad0dc..0e3814ae7 100644 --- a/tests/test_nash.py +++ b/tests/test_nash.py @@ -141,6 +141,5 @@ def test_logit_zerochance(): 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) - assert result.equilibria[0][g.players["Alice"].infosets[0].actions[0]] == 1 - assert result.equilibria[0][g.players["Alice"].infosets[0].actions[1]] == 0 + result = gbt.nash.logit_solve(g, use_strategic=False, maxregret=0.0001) + assert result.equilibria[0].max_regret() < 0.0001