Skip to content

Commit

Permalink
Prefer simplicial subdivision as recommended method for some Nash.
Browse files Browse the repository at this point in the history
* Changes the "recommended method" in the graphical interface for computing
  some equilibria of a game with more than two players, from `gambit-liap`
  to `gambit-simpdiv`
* Adds an option to report equilibria found by `gambit-simpdiv` on the command line as floating-point
  probabilities rather than rationals.

The latter closes #296.
  • Loading branch information
tturocy committed Mar 28, 2024
1 parent 0acdf85 commit bdc3936
Show file tree
Hide file tree
Showing 4 changed files with 63 additions and 13 deletions.
5 changes: 4 additions & 1 deletion ChangeLog
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
- Additional regret-related functions added to `MixedBehaviorProfile` and `MixedStrategyProfile`
in both C++ and Python
- Some caching added to payoff/strategy value calculations in `MixedStrategyProfile`
- `gambit-simpdiv` now supports expressing output as floating-point with a specified number of
digits (#296)

### Changed
- Gambit now requires a compiler that supports C++17.
Expand All @@ -45,7 +47,8 @@
Creation of random mixed profiles in C++ is done with new `Game::NewRandomStrategyProfile` and
`Game::NewRandomBehaviorProfile` methods; these accept STL `Generator` objects for reproducible state.
The Python implementation is no longer just a wrapper around the C++ one.

- Graphical interface now uses simplicial subdivision as the recommended method for finding some
equilibria in games with more than two players, instead of Lyapunov function minimisation

## [16.1.1] - 2024-01-10

Expand Down
11 changes: 11 additions & 0 deletions doc/tools.simpdiv.rst
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,17 @@ options to specify additional starting points for the algorithm.
Specify the maximum regret criterion for acceptance as an approximate Nash equilibrium
(default is 1e-8). See :ref:`pygambit-nash-maxregret` for interpretation and guidance.

.. cmdoption:: -d DECIMALS

.. versionadded:: 16.2.0

Simplicial subdivision operates on a triangulation grid in the set of mixed strategy profiles.
Therefore, it produces output in which all probabilities are expressed as rational numbers, and
by default the output reports these. By specifying this option, instead probabilities are
expressed as floating-point numbers with the specified number of decimal places. Specifying
this option sacrifices some precision in reporting the output of the method, in exchange for
probabilities which are more human-readable.

.. cmdoption:: -v

Sets verbose mode. In verbose mode, initial points, as well as
Expand Down
12 changes: 6 additions & 6 deletions src/gui/dlnash.cc
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,14 @@ gbtNashChoiceDialog::gbtNashChoiceDialog(wxWindow *p_parent, gbtGameDocument *p_

if (m_doc->GetGame()->NumPlayers() == 2) {
wxString countChoices[] = {wxT("Compute one Nash equilibrium"),
wxT("Compute as many Nash equilibria as possible"),
wxT("Compute some Nash equilibria"),
wxT("Compute all Nash equilibria")};
m_countChoice =
new wxChoice(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, 3, countChoices);
}
else {
wxString countChoices[] = {wxT("Compute one Nash equilibrium"),
wxT("Compute as many Nash equilibria as possible")};
wxT("Compute some Nash equilibria")};
m_countChoice =
new wxChoice(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, 2, countChoices);
}
Expand Down Expand Up @@ -213,9 +213,9 @@ gbtAnalysisOutput *gbtNashChoiceDialog::GetCommand() const
wxT("Some equilibria by solving a linear ") wxT("complementarity program ") + game);
}
else {
cmd = new gbtAnalysisProfileList<double>(m_doc, useEfg);
cmd->SetCommand(prefix + wxT("liap -d 10") + options);
cmd->SetDescription(wxT("Some equilibria by function minimization ") + game);
cmd = new gbtAnalysisProfileList<double>(m_doc, false);
cmd->SetCommand(prefix + wxT("simpdiv -d 10 -n 20 -r 100") + options);
cmd->SetDescription(wxT("Some equilibria by simplicial subdivision ") + game);
}
}
else {
Expand Down Expand Up @@ -285,7 +285,7 @@ gbtAnalysisOutput *gbtNashChoiceDialog::GetCommand() const
}
else if (method == s_simpdiv) {
cmd = new gbtAnalysisProfileList<double>(m_doc, false);
cmd->SetCommand(prefix + wxT("simpdiv") + options);
cmd->SetCommand(prefix + wxT("simpdiv -d 10 -n 20 -r 100") + options);
cmd->SetDescription(count + wxT(" by simplicial subdivision ") wxT("in strategic game"));
}
else {
Expand Down
48 changes: 42 additions & 6 deletions src/tools/simpdiv/nfgsimpdiv.cc
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,31 @@ List<MixedStrategyProfile<Rational>> RandomProfiles(const Game &p_game, int p_co
return profiles;
}

class MixedStrategyCSVAsFloatRenderer : public MixedStrategyRenderer<Rational> {
public:
explicit MixedStrategyCSVAsFloatRenderer(std::ostream &p_stream, int p_numDecimals = 6)
: m_stream(p_stream), m_numDecimals(p_numDecimals)
{
}
~MixedStrategyCSVAsFloatRenderer() override = default;
void Render(const MixedStrategyProfile<Rational> &p_profile,
const std::string &p_label = "NE") const override;

private:
std::ostream &m_stream;
int m_numDecimals;
};

void MixedStrategyCSVAsFloatRenderer::Render(const MixedStrategyProfile<Rational> &p_profile,
const std::string &p_label) const
{
m_stream << p_label;
for (size_t i = 1; i <= p_profile.MixedProfileLength(); i++) {
m_stream << "," << lexical_cast<std::string>(double(p_profile[i]), m_numDecimals);
}
m_stream << std::endl;
}

void PrintBanner(std::ostream &p_stream)
{
p_stream << "Compute Nash equilibria using simplicial subdivision\n";
Expand All @@ -86,6 +111,8 @@ void PrintHelp(char *progname)
std::cerr << " -r DENOM generate random starting points with denominator DENOM\n";
std::cerr << " -n COUNT number of starting points to generate (requires -r)\n";
std::cerr << " -s FILE file containing starting points\n";
std::cerr << " -d DECIMALS show profiles as floating point with DECIMALS digits\n";
std::cerr << " (default is to display rational numbers)\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";
Expand All @@ -100,7 +127,7 @@ int main(int argc, char *argv[])
opterr = 0;
std::string startFile;
bool useRandom = false;
int randDenom = 1, gridResize = 2, stopAfter = 1;
int randDenom = 1, gridResize = 2, stopAfter = 1, decimals = 0;
bool verbose = false, quiet = false;
Rational maxregret(1, 10000000);

Expand All @@ -110,7 +137,7 @@ int main(int argc, char *argv[])
{"verbose", 0, nullptr, 'V'},
{nullptr, 0, nullptr, 0}};
int c;
while ((c = getopt_long(argc, argv, "g:hVvn:r:s:m:qS", long_options, &long_opt_index)) != -1) {
while ((c = getopt_long(argc, argv, "g:hVvn:r:s:m:d:qS", long_options, &long_opt_index)) != -1) {
switch (c) {
case 'v':
PrintBanner(std::cerr);
Expand All @@ -131,6 +158,9 @@ int main(int argc, char *argv[])
case 'm':
maxregret = lexical_cast<Rational>(std::string(optarg));
break;
case 'd':
decimals = atoi(optarg);
break;
case 's':
startFile = optarg;
break;
Expand Down Expand Up @@ -190,10 +220,16 @@ int main(int argc, char *argv[])
}
}
for (auto start : starts) {
std::shared_ptr<StrategyProfileRenderer<Rational>> renderer(
new MixedStrategyCSVRenderer<Rational>(std::cout));
NashSimpdivStrategySolver algorithm(gridResize, 0, maxregret, verbose, renderer);
algorithm.Solve(start);
if (decimals > 0) {
auto renderer = std::make_shared<MixedStrategyCSVAsFloatRenderer>(std::cout, decimals);
NashSimpdivStrategySolver algorithm(gridResize, 0, maxregret, verbose, renderer);
algorithm.Solve(start);
}
else {
auto renderer = std::make_shared<MixedStrategyCSVRenderer<Rational>>(std::cout);
NashSimpdivStrategySolver algorithm(gridResize, 0, maxregret, verbose, renderer);
algorithm.Solve(start);
}
}
return 0;
}
Expand Down

0 comments on commit bdc3936

Please sign in to comment.