From cd4f5bb50bdfcd9b8083b3d5e566a4d6663b9478 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Fern=C3=A1ndez=20Cervell?= Date: Wed, 1 Jul 2026 14:19:14 +0200 Subject: [PATCH 1/4] Add shell infrastructure for Herings-Peeters (2001) solver --- Makefile.am | 2 ++ doc/references.bib | 10 ++++++++++ setup.py | 3 ++- src/pygambit/gambit.pxd | 4 ++++ src/pygambit/nash.pxi | 4 ++++ src/pygambit/nash.py | 29 +++++++++++++++++++++++++++++ src/solvers/hp/hp.cc | 38 ++++++++++++++++++++++++++++++++++++++ src/solvers/hp/hp.h | 32 ++++++++++++++++++++++++++++++++ 8 files changed, 121 insertions(+), 1 deletion(-) create mode 100644 src/solvers/hp/hp.cc create mode 100644 src/solvers/hp/hp.h diff --git a/Makefile.am b/Makefile.am index 5ef18af9bd..158f58a5fb 100644 --- a/Makefile.am +++ b/Makefile.am @@ -207,6 +207,8 @@ EXTRA_DIST = \ contrib/games/yamamoto.nfg \ contrib/games/zero.nfg \ src/README.rst \ + src/solvers/hp/hp.cc \ + src/solvers/hp/hp.h \ $(CATALOG_FILES) core_SOURCES = \ diff --git a/doc/references.bib b/doc/references.bib index 4c17e1cf88..0756bfbb46 100644 --- a/doc/references.bib +++ b/doc/references.bib @@ -38,6 +38,16 @@ @article{GovWil04 category = {articles_equilibria} } +@article{HerPee01, + author = {Herings, P. J.-J. and Peeters, R. J. A. P.}, + title = {A differentiable homotopy to compute {N}ash equilibria of n-person games}, + journal = {Economic Theory}, + volume = {18}, + pages = {159--185}, + year = {2001}, + category = {articles_equilibria} +} + @article{Jiang11, author = {Jiang, A. X. and Leyton-Brown, K. and Bhat, N.}, title = {Action-graph games}, diff --git a/setup.py b/setup.py index 9b47eb5333..945b827147 100644 --- a/setup.py +++ b/setup.py @@ -100,6 +100,7 @@ def run(self) -> None: 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"]) +cppgambit_hp = solver_library_config("cppgambit_hp", ["hp"]) libgambit = setuptools.Extension( @@ -117,7 +118,7 @@ def run(self) -> None: setuptools.setup( cmdclass={"build_py": GambitBuildPy}, libraries=[cppgambit_bimatrix, cppgambit_liap, cppgambit_logit, cppgambit_simpdiv, - cppgambit_gtracer, cppgambit_enumpoly, + cppgambit_gtracer, cppgambit_enumpoly, cppgambit_hp, cppgambit_games, cppgambit_core], ext_modules=Cython.Build.cythonize(libgambit, language_level="3str", diff --git a/src/pygambit/gambit.pxd b/src/pygambit/gambit.pxd index 62e83e8312..33c47c480f 100644 --- a/src/pygambit/gambit.pxd +++ b/src/pygambit/gambit.pxd @@ -621,6 +621,10 @@ cdef extern from "solvers/logit/logit.h": double getitem "operator[]"(int) except +IndexError +cdef extern from "solvers/hp/hp.h": + stdlist[c_MixedStrategyProfile[double]] HPStrategySolveWrapper(c_Game) except +RuntimeError + + cdef extern from "nash.h": stdlist[c_MixedBehaviorProfile[double]] LogitBehaviorSolveWrapper( c_Game, double, double, double diff --git a/src/pygambit/nash.pxi b/src/pygambit/nash.pxi index 00c74cf3b9..7743856c53 100644 --- a/src/pygambit/nash.pxi +++ b/src/pygambit/nash.pxi @@ -368,3 +368,7 @@ def _logit_behavior_branch(game: Game, p.thisptr = profile_ptr ret.append(p) return ret + + +def _hp_strategy_solve(game: Game) -> list[MixedStrategyProfileDouble]: + return _convert_mspd(HPStrategySolveWrapper(game.game)) diff --git a/src/pygambit/nash.py b/src/pygambit/nash.py index 96201df0b6..a233448dfe 100644 --- a/src/pygambit/nash.py +++ b/src/pygambit/nash.py @@ -804,3 +804,32 @@ def logit_solve( equilibria=equilibria, parameters={"first_step": first_step, "max_accel": max_accel}, ) + + +def hp_solve( + game: libgbt.Game, +) -> NashComputationResult: + """Compute Nash equilibria of a game using :cite:p:`HerPee01` + + Returns an approximation to the limiting point on the principal branch of + the homotopy path for the game. + + Parameters + ---------- + game : Game + The game to compute equilibria in. + + Returns + ------- + res : NashComputationResult + The result represented as a ``NashComputationResult`` object. + """ + equilibria = libgbt._hp_strategy_solve(game) + return NashComputationResult( + game=game, + method="hp", + rational=False, + use_strategic=True, + equilibria=equilibria, + parameters={}, + ) diff --git a/src/solvers/hp/hp.cc b/src/solvers/hp/hp.cc new file mode 100644 index 0000000000..ff83faecfe --- /dev/null +++ b/src/solvers/hp/hp.cc @@ -0,0 +1,38 @@ +// +// This file is part of Gambit +// Copyright (c) 1994-2026, The Gambit Project (https://www.gambit-project.org) +// +// FILE: src/solvers/hp/hp.cc +// Computation of a Nash equilibria using a differentiable homotopy +// +// 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 "solvers/hp/hp.h" + +namespace Gambit { +std::list> HPStrategySolveWrapper(const Game &p_game) +{ + std::list> result; + const StrategySupportProfile support(p_game); + + const MixedStrategyProfile trivial_profile = support.NewMixedStrategyProfile(); + + result.push_back(trivial_profile); + return result; +} +} // namespace Gambit diff --git a/src/solvers/hp/hp.h b/src/solvers/hp/hp.h new file mode 100644 index 0000000000..70f1a46c10 --- /dev/null +++ b/src/solvers/hp/hp.h @@ -0,0 +1,32 @@ +// +// This file is part of Gambit +// Copyright (c) 1994-2026, The Gambit Project (http://www.gambit-project.org) +// +// FILE: src/solvers/hp/hp.h +// Computation of a Nash equilibria using a differentiable homotopy +// +// 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 HP_H +#define HP_H + +#include + +namespace Gambit { +std::list> HPStrategySolveWrapper(const Game &p_game); +} // namespace Gambit + +#endif // HP_H From 230cf0398bb18d09b467730121c10f3e1f505424 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Fern=C3=A1ndez=20Cervell?= <153729439+AndresFerCervell@users.noreply.github.com> Date: Wed, 1 Jul 2026 14:58:25 +0200 Subject: [PATCH 2/4] Update references.bib with new article entry --- doc/references.bib | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/references.bib b/doc/references.bib index f8cfdd1b99..36ae6110e6 100644 --- a/doc/references.bib +++ b/doc/references.bib @@ -46,6 +46,7 @@ @article{HerPee01 pages = {159--185}, year = {2001}, category = {articles_equilibria} +} @article{HalPas21, author = {Halpern, J. Y. and Pass, R.}, From 4b6492becff92906731a9c835507af07ff21853e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Fern=C3=A1ndez=20Cervell?= <153729439+AndresFerCervell@users.noreply.github.com> Date: Wed, 1 Jul 2026 17:23:58 +0200 Subject: [PATCH 3/4] Update Makefile.am --- Makefile.am | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Makefile.am b/Makefile.am index 158f58a5fb..f2ea3fda3d 100644 --- a/Makefile.am +++ b/Makefile.am @@ -207,8 +207,6 @@ EXTRA_DIST = \ contrib/games/yamamoto.nfg \ contrib/games/zero.nfg \ src/README.rst \ - src/solvers/hp/hp.cc \ - src/solvers/hp/hp.h \ $(CATALOG_FILES) core_SOURCES = \ @@ -315,6 +313,10 @@ gtracer_SOURCES = \ src/solvers/gtracer/gnm.cc \ src/solvers/gtracer/ipa.cc +hp_SOURCES = \ + src/solvers/hp/hp.h \ + src/solvers/hp/hp.cc + if IS_WIN32 AM_LDFLAGS = -static -static-libgcc -static-libstdc++ endif From 6562ee0d80956a9b237c645e627c02c849b433fe Mon Sep 17 00:00:00 2001 From: Theodore Turocy Date: Fri, 3 Jul 2026 09:07:00 +0100 Subject: [PATCH 4/4] Standardise algorithm function name --- src/pygambit/gambit.pxd | 2 +- src/pygambit/nash.pxi | 2 +- src/solvers/hp/hp.cc | 2 +- src/solvers/hp/hp.h | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/pygambit/gambit.pxd b/src/pygambit/gambit.pxd index d71a99f65d..6442287340 100644 --- a/src/pygambit/gambit.pxd +++ b/src/pygambit/gambit.pxd @@ -622,7 +622,7 @@ cdef extern from "solvers/logit/logit.h": cdef extern from "solvers/hp/hp.h": - stdlist[c_MixedStrategyProfile[double]] HPStrategySolveWrapper(c_Game) except +RuntimeError + stdlist[c_MixedStrategyProfile[double]] HPStrategySolve(c_Game) except +RuntimeError cdef extern from "nash.h": diff --git a/src/pygambit/nash.pxi b/src/pygambit/nash.pxi index 7743856c53..881bd99e15 100644 --- a/src/pygambit/nash.pxi +++ b/src/pygambit/nash.pxi @@ -371,4 +371,4 @@ def _logit_behavior_branch(game: Game, def _hp_strategy_solve(game: Game) -> list[MixedStrategyProfileDouble]: - return _convert_mspd(HPStrategySolveWrapper(game.game)) + return _convert_mspd(HPStrategySolve(game.game)) diff --git a/src/solvers/hp/hp.cc b/src/solvers/hp/hp.cc index ff83faecfe..15e57b6e33 100644 --- a/src/solvers/hp/hp.cc +++ b/src/solvers/hp/hp.cc @@ -25,7 +25,7 @@ #include "solvers/hp/hp.h" namespace Gambit { -std::list> HPStrategySolveWrapper(const Game &p_game) +std::list> HPStrategySolve(const Game &p_game) { std::list> result; const StrategySupportProfile support(p_game); diff --git a/src/solvers/hp/hp.h b/src/solvers/hp/hp.h index 70f1a46c10..d51a0dc6c4 100644 --- a/src/solvers/hp/hp.h +++ b/src/solvers/hp/hp.h @@ -26,7 +26,7 @@ #include namespace Gambit { -std::list> HPStrategySolveWrapper(const Game &p_game); +std::list> HPStrategySolve(const Game &p_game); } // namespace Gambit #endif // HP_H