diff --git a/Makefile.am b/Makefile.am index 7f6d2d87b..8f8bd8e91 100644 --- a/Makefile.am +++ b/Makefile.am @@ -496,6 +496,7 @@ gambit_SOURCES = \ src/gui/dlnash.cc \ src/gui/dlnash.h \ src/gui/dlnashmon.cc \ + src/gui/nashspec.h \ src/gui/dlnewtable.cc \ src/gui/dlnewtable.h \ src/gui/dlnfglogit.cc \ diff --git a/doc/algorithms.rst b/doc/algorithms.rst index 0f4b33260..260ea688d 100644 --- a/doc/algorithms.rst +++ b/doc/algorithms.rst @@ -15,7 +15,7 @@ Algorithm Description :ref:`lp` Compute equilibria in a two-player constant-sum game via linear programming :py:func:`pygambit.nash.lp_solve` :ref:`gambit-lp ` :ref:`lcp` Compute equilibria in a two-player game via linear complementarity :py:func:`pygambit.nash.lcp_solve` :ref:`gambit-lcp ` :ref:`liap` Compute equilibria using function minimization :py:func:`pygambit.nash.liap_solve` :ref:`gambit-liap ` -:ref:`logit` Compute quantal response equilibria :py:func:`pygambit.nash.logit_solve` :ref:`gambit-logit ` +:ref:`logit` Trace logit QRE and approximate a Nash equilibrium at high precision :py:func:`pygambit.nash.logit_solve` :ref:`gambit-logit ` :ref:`simpdiv` Compute equilibria via simplicial subdivision :py:func:`pygambit.nash.simpdiv_solve` :ref:`gambit-simpdiv ` :ref:`ipa` Compute equilibria using iterated polymatrix approximation :py:func:`pygambit.nash.ipa_solve` :ref:`gambit-ipa ` :ref:`gnm` Compute equilibria using a global Newton method :py:func:`pygambit.nash.gnm_solve` :ref:`gambit-gnm ` @@ -141,6 +141,10 @@ logit Computes the principal branch of the (logit) quantal response correspondence. +Following this branch toward large values of the precision parameter +provides an approximation to a Nash equilibrium. The graphical +interface uses this limiting calculation when it selects ``logit`` as +the recommended method for computing one Nash equilibrium. The method is based on the procedure described in Turocy :cite:p:`Tur05` for strategic games and Turocy :cite:p:`Tur10` for extensive games. diff --git a/doc/gui.nash.rst b/doc/gui.nash.rst index 1891dad4c..b9b9a200f 100644 --- a/doc/gui.nash.rst +++ b/doc/gui.nash.rst @@ -38,22 +38,62 @@ are to be found. Some algorithms for computing equilibria are adapted to finding a single equilibrium, while others attempt to compute the whole equilibrium set. The first drop-down in the dialog specifies how many equilibria to compute. In this drop-down there are options for -:guilabel:`as many equilibria as possible` and, for two-player games, -:guilabel:`all equilibria`. For some games, there exist algorithms which will +:guilabel:`Compute one Nash equilibrium`, :guilabel:`Compute some Nash equilibria`, +and, for two-player games, :guilabel:`Compute all Nash equilibria`. +For some games, there exist algorithms which will compute many equilibria (relatively) efficiently, but are not guaranteed to find all equilibria. -To simplify this process of choosing the method to compute equilibria -in the second drop-down, Gambit provides for any game "recommended" -methods for computing one, some, and all Nash equilibria, -respectively. These methods are selected based on experience as to the -efficiency and reliability of the methods, and should generally work -well on most games. For more control over the process, the user can -select from the second drop-down in the dialog one of the appropriate -methods for computing equilibria. This list only shows the methods -which are appropriate for the game, given the selection of how many -equilibria to compute. More details on these methods are contained -in :ref:`command-line`. +To simplify this process of choosing the method to compute equilibria, +the second drop-down offers :guilabel:`Gambit's recommended method`. +The recommendation is determined by the requested number of equilibria +and the structure of the game as follows. + +======================= ============================================= ================== +Requested output Game Method +======================= ============================================= ================== +One equilibrium Two players and constant sum :ref:`lp` +One equilibrium Any other supported game :ref:`logit` +Some equilibria Two players :ref:`lcp` +Some equilibria Three or more players :ref:`simpdiv` +All equilibria Two players :ref:`enummixed` +======================= ============================================= ================== + +The :guilabel:`all equilibria` choice is therefore offered only for +two-player games. The recommendation selects an algorithm; it does not +infer properties such as uniqueness. In particular, "some" means that +the selected method searches for multiple equilibria where possible, not +that a particular number of distinct equilibria is guaranteed. + +For more control, the user can select one of the other methods shown in +the second drop-down. The list is filtered using the requested output +category and the methods' applicability. Methods which only operate on +strategic games automatically select and lock the strategic +representation. Otherwise an extensive game may be solved using either +its behavior representation or its reduced strategic representation. +See :ref:`algorithms` for descriptions and limitations of the methods. + +The current dialog uses the following solver parameters. They are +recorded with each computation so that the interface can expose them for +editing in a future revision. + +================ ============================================================================ +Method Parameters used by the graphical interface +================ ============================================================================ +``enumpure`` Enumerate all pure-strategy equilibria. +``enummixed`` Enumerate all mixed-strategy extreme equilibria. +``enumpoly`` Stop after the first equilibrium when "one" is requested; otherwise search all + supports; maximum regret ``1e-4``. +``lp`` No method-specific parameters. +``lcp`` Search all accessible equilibria; unlimited recursion depth. +``liap`` 10 random starting points; 1,000 iterations per point; maximum regret ``1e-4``. +``logit`` Maximum regret ``1e-8``; initial step ``0.03``; maximum acceleration ``1.1``. +``simpdiv`` One random starting point when "one" is requested, otherwise 20; denominator + 100; grid resize factor 2; maximum regret ``1/10000000``. +``ipa`` One random perturbation. +``gnm`` One random perturbation; ending lambda ``-10``; 100 steps per support cell; + local Newton refinement every 3 steps, with at most 10 iterations. +================ ============================================================================ .. image:: screens/computing.* :width: 33% @@ -61,11 +101,12 @@ in :ref:`command-line`. :align: right :target: _images/computing.png -Finally, for extensive games, there is an option of whether to use the -extensive or strategic game for computation. In general, computation +For extensive games, there is an option of whether to use the +extensive or strategic game for computation when the selected method +supports both. In general, computation using the extensive game is preferred, since it is often a significantly more compact representation of the strategic -characeteristics of the game than the reduced strategic game is. +characteristics of the game than the reduced strategic game is. For even moderate sized games, computation of equilibrium can be a time-intensive process. Gambit runs all computations in the diff --git a/src/gui/analysis.h b/src/gui/analysis.h index 0bf531efb..c11a9a5fc 100644 --- a/src/gui/analysis.h +++ b/src/gui/analysis.h @@ -23,7 +23,10 @@ #ifndef GAMBIT_GUI_ANALYSIS_H #define GAMBIT_GUI_ANALYSIS_H +#include + #include "games/workspace.h" +#include "nashspec.h" // // This file contains classes which manage the output of analysis tools. @@ -41,6 +44,7 @@ class AnalysisOutput { protected: GameDocument *m_doc; wxString m_label, m_description, m_command; + std::optional m_computation; public: /// @name Lifecycle @@ -71,6 +75,15 @@ class AnalysisOutput { /// Set the command used to generate the list void SetCommand(const wxString &p_command) { m_command = p_command; } + /// Get the typed specification used to generate the list, if available + const std::optional &GetComputationSpec() const { return m_computation; } + + /// Set the typed specification used to generate the list + void SetComputationSpec(const NashComputationSpec &p_computation) + { + m_computation = p_computation; + } + /// The number of profiles in the list virtual int NumProfiles() const = 0; diff --git a/src/gui/dlnash.cc b/src/gui/dlnash.cc index afc6b84ef..ef684718b 100644 --- a/src/gui/dlnash.cc +++ b/src/gui/dlnash.cc @@ -21,6 +21,8 @@ // #include +#include +#include #ifndef WX_PRECOMP #include @@ -42,6 +44,261 @@ static wxString s_liap(wxT("by minimizing the Lyapunov function")); static wxString s_logit(wxT("by tracing logit equilibria")); static wxString s_simpdiv(wxT("by simplicial subdivision")); +namespace { + +NashEquilibriumTarget GetTarget(int p_selection) +{ + if (p_selection == 0) { + return NashEquilibriumTarget::One; + } + if (p_selection == 1) { + return NashEquilibriumTarget::Some; + } + return NashEquilibriumTarget::All; +} + +NashMethodSpec ResolveMethod(const wxString &p_method, NashEquilibriumTarget p_target, + const Game &p_game) +{ + if (p_method == s_recommended) { + if (p_target == NashEquilibriumTarget::One) { + if (p_game->NumPlayers() == 2 && p_game->IsConstSum()) { + return LPNashSpec{}; + } + return LogitNashSpec{}; + } + if (p_target == NashEquilibriumTarget::Some) { + if (p_game->NumPlayers() == 2) { + return LCPNashSpec{}; + } + return SimpdivNashSpec{}; + } + return EnumMixedNashSpec{}; + } + if (p_method == s_enumpure) { + return EnumPureNashSpec{}; + } + if (p_method == s_enummixed) { + return EnumMixedNashSpec{}; + } + if (p_method == s_enumpoly) { + auto spec = EnumPolyNashSpec{}; + if (p_target == NashEquilibriumTarget::One) { + spec.stopAfter = 1; + } + return spec; + } + if (p_method == s_gnm) { + return GNMNashSpec{}; + } + if (p_method == s_ipa) { + return IPANashSpec{}; + } + if (p_method == s_lp) { + return LPNashSpec{}; + } + if (p_method == s_lcp) { + return LCPNashSpec{}; + } + if (p_method == s_liap) { + return LiapNashSpec{}; + } + if (p_method == s_logit) { + return LogitNashSpec{}; + } + if (p_method == s_simpdiv) { + auto spec = SimpdivNashSpec{}; + if (p_target == NashEquilibriumTarget::One) { + spec.startingPoints = 1; + } + return spec; + } + throw std::logic_error("Unknown Nash equilibrium method"); +} + +bool RequiresStrategicRepresentation(const NashMethodSpec &p_method) +{ + return std::visit( + [](const auto &method) { + using Method = std::decay_t; + return std::is_same_v || + std::is_same_v || std::is_same_v || + std::is_same_v || std::is_same_v || + std::is_same_v; + }, + p_method); +} + +bool UsesRationalOutput(const NashMethodSpec &p_method) +{ + return std::visit( + [](const auto &method) { + using Method = std::decay_t; + return std::is_same_v || + std::is_same_v || std::is_same_v || + std::is_same_v; + }, + p_method); +} + +wxString ExternalCommand(const NashComputationSpec &p_spec) +{ +#ifdef __WXMAC__ + wxString prefix = wxStandardPaths::Get().GetExecutablePath() + wxT("-"); +#else + wxString prefix = wxT("gambit-"); +#endif + const wxString strategic = + p_spec.representation == NashRepresentation::Strategic ? wxT(" -S") : wxString{}; + + return std::visit( + [&](const auto &method) { + using Method = std::decay_t; + if constexpr (std::is_same_v) { + return prefix + wxT("enumpure") + strategic; + } + else if constexpr (std::is_same_v) { + return prefix + wxT("enummixed") + strategic; + } + else if constexpr (std::is_same_v) { + return prefix + + wxString::Format("enumpoly -d 10 -e %d -m %.17g", method.stopAfter, + method.maxRegret) + + strategic; + } + else if constexpr (std::is_same_v) { + return prefix + + wxString::Format("gnm -d 10 -n %d -m %.17g -c %d -f %d -i %d", + method.perturbations, method.lambdaEnd, method.steps, + method.localNewtonInterval, method.localNewtonMaxIterations) + + strategic; + } + else if constexpr (std::is_same_v) { + return prefix + wxString::Format("ipa -d 10 -n %d", method.perturbations) + strategic; + } + else if constexpr (std::is_same_v) { + return prefix + wxT("lp") + strategic; + } + else if constexpr (std::is_same_v) { + return prefix + wxString::Format("lcp -e %d -r %d", method.stopAfter, method.maxDepth) + + strategic; + } + else if constexpr (std::is_same_v) { + return prefix + + wxString::Format("liap -d 10 -n %d -i %d -m %.17g", method.startingPoints, + method.maxIterations, method.maxRegret) + + strategic; + } + else if constexpr (std::is_same_v) { + return prefix + + wxString::Format("logit -e -d 10 -m %.17g -s %.17g -a %.17g", method.maxRegret, + method.firstStep, method.maxAcceleration) + + strategic; + } + else { + std::ostringstream regret; + regret << method.maxRegret; + return prefix + + wxString::Format("simpdiv -d 10 -n %d -r %d -g %d -m ", method.startingPoints, + method.randomDenominator, method.gridResize) + + wxString(regret.str()) + strategic; + } + }, + p_spec.method); +} + +wxString MethodDescription(const NashMethodSpec &p_method) +{ + return std::visit( + [](const auto &method) { + using Method = std::decay_t; + if constexpr (std::is_same_v) { + return wxT("in pure strategies"); + } + else if constexpr (std::is_same_v) { + return wxT("by enumeration of mixed-strategy extreme points"); + } + else if constexpr (std::is_same_v) { + return wxT("by solving polynomial systems"); + } + else if constexpr (std::is_same_v) { + return wxT("by global Newton tracing"); + } + else if constexpr (std::is_same_v) { + return wxT("by iterated polymatrix approximation"); + } + else if constexpr (std::is_same_v) { + return wxT("by solving a linear program"); + } + else if constexpr (std::is_same_v) { + return wxT("by solving a linear complementarity program"); + } + else if constexpr (std::is_same_v) { + return wxT("by function minimization"); + } + else if constexpr (std::is_same_v) { + return wxT("by logit tracing"); + } + else { + return wxT("by simplicial subdivision"); + } + }, + p_method); +} + +wxString ParameterDescription(const NashMethodSpec &p_method) +{ + return std::visit( + [](const auto &method) { + using Method = std::decay_t; + if constexpr (std::is_same_v) { + if (method.stopAfter == 1) { + return wxString::Format(" (stop after one equilibrium; maximum regret %.4g)", + method.maxRegret); + } + return wxString::Format(" (all supports; maximum regret %.4g)", method.maxRegret); + } + else if constexpr (std::is_same_v) { + return wxString::Format( + " (%d perturbation; ending lambda %.4g; %d steps per support cell; local Newton " + "every %d steps, at most %d iterations)", + method.perturbations, method.lambdaEnd, method.steps, method.localNewtonInterval, + method.localNewtonMaxIterations); + } + else if constexpr (std::is_same_v) { + return wxString::Format(" (%d perturbation)", method.perturbations); + } + else if constexpr (std::is_same_v) { + return wxString(wxT(" (all accessible equilibria; unlimited recursion depth)")); + } + else if constexpr (std::is_same_v) { + return wxString::Format(" (%d random starting points; at most %d iterations; maximum " + "regret %.4g)", + method.startingPoints, method.maxIterations, method.maxRegret); + } + else if constexpr (std::is_same_v) { + return wxString::Format( + " (maximum regret %.4g; initial step %.4g; maximum acceleration %.4g)", + method.maxRegret, method.firstStep, method.maxAcceleration); + } + else if constexpr (std::is_same_v) { + std::ostringstream regret; + regret << method.maxRegret; + return wxString::Format(" (%d random starting points with denominator %d; grid resize " + "factor %d; maximum regret ", + method.startingPoints, method.randomDenominator, + method.gridResize) + + wxString(regret.str()) + wxT(")"); + } + else { + return wxString{}; + } + }, + p_method); +} + +} // namespace + NashChoiceDialog::NashChoiceDialog(wxWindow *p_parent, GameDocument *p_doc) : wxDialog(p_parent, wxID_ANY, wxT("Compute Nash equilibria"), wxDefaultPosition), m_doc(p_doc) { @@ -94,6 +351,8 @@ NashChoiceDialog::NashChoiceDialog(wxWindow *p_parent, GameDocument *p_doc) m_repChoice = nullptr; } + UpdateRepresentationChoice(); + auto *buttonSizer = new wxBoxSizer(wxHORIZONTAL); buttonSizer->Add(new wxButton(this, wxID_CANCEL, _("Cancel")), 0, wxALL, 5); auto *okButton = new wxButton(this, wxID_OK, _("OK")); @@ -119,6 +378,7 @@ void NashChoiceDialog::OnCount(wxCommandEvent &p_event) } m_methodChoice->Append(s_simpdiv); m_methodChoice->Append(s_logit); + m_methodChoice->Append(s_enumpoly); } else if (p_event.GetSelection() == 1) { if (m_doc->GetGame()->NumPlayers() == 2) { @@ -136,14 +396,19 @@ void NashChoiceDialog::OnCount(wxCommandEvent &p_event) } } m_methodChoice->SetSelection(0); + UpdateRepresentationChoice(); } -void NashChoiceDialog::OnMethod(wxCommandEvent &p_event) -{ +void NashChoiceDialog::OnMethod(wxCommandEvent &) { UpdateRepresentationChoice(); } - if (const wxString method = m_methodChoice->GetString(p_event.GetSelection()); - method == s_enumpure || method == s_simpdiv || method == s_enummixed || method == s_liap || - method == s_gnm || method == s_ipa) { +void NashChoiceDialog::UpdateRepresentationChoice() +{ + if (!m_repChoice) { + return; + } + const auto method = ResolveMethod(m_methodChoice->GetStringSelection(), + GetTarget(m_countChoice->GetSelection()), m_doc->GetGame()); + if (RequiresStrategicRepresentation(method)) { m_repChoice->SetSelection(1); m_repChoice->Enable(false); } @@ -154,139 +419,51 @@ void NashChoiceDialog::OnMethod(wxCommandEvent &p_event) bool NashChoiceDialog::UseStrategic() const { - return (m_repChoice == nullptr || m_repChoice->GetSelection() == 1); + return GetComputation().representation == NashRepresentation::Strategic; } -std::shared_ptr NashChoiceDialog::GetCommand() const +NashComputationSpec NashChoiceDialog::GetComputation() const { - const bool useEfg = m_repChoice && m_repChoice->GetSelection() == 0; - std::shared_ptr cmd = nullptr; - - const wxString method = m_methodChoice->GetStringSelection(); - - wxString prefix, options, game, count; -#ifdef __WXMAC__ - // Look in the app bundle. The command-line tools should be placed - // in the same folder inside the app bundle as the GUI executable. - // GetExecutablePath() returns the full path to the GUI executable, - // including the 'gambit', so all we need is the dash to form the prefix. - prefix = wxStandardPaths::Get().GetExecutablePath() + wxT("-"); -#else - prefix = wxT("gambit-"); -#endif // __WXMAC__ - - if (useEfg) { - game = wxT("in extensive game"); - } - else { - options = wxT(" -S "); - game = wxT("in strategic game"); - } + const auto target = GetTarget(m_countChoice->GetSelection()); + auto method = ResolveMethod(m_methodChoice->GetStringSelection(), target, m_doc->GetGame()); + const auto representation = + !m_repChoice || m_repChoice->GetSelection() == 1 || RequiresStrategicRepresentation(method) + ? NashRepresentation::Strategic + : NashRepresentation::Behavior; + return {representation, target, std::move(method), + m_methodChoice->GetStringSelection() == s_recommended}; +} - if (m_countChoice->GetSelection() == 0) { - count = wxT("One equilibrium"); - } - else if (m_countChoice->GetSelection() == 1) { - count = wxT("Some equilibria"); +std::shared_ptr NashChoiceDialog::GetCommand() const +{ + const auto computation = GetComputation(); + const bool useBehavior = computation.representation == NashRepresentation::Behavior; + std::shared_ptr output; + if (UsesRationalOutput(computation.method)) { + output = std::make_shared>(m_doc, useBehavior); } else { - count = wxT("All equilibria"); + output = std::make_shared>(m_doc, useBehavior); } - if (method == s_recommended) { - if (m_countChoice->GetSelection() == 0) { - if (m_doc->GetGame()->NumPlayers() == 2 && m_doc->GetGame()->IsConstSum()) { - cmd = std::make_shared>(m_doc, useEfg); - cmd->SetCommand(prefix + wxT("lp") + options); - cmd->SetDescription(wxT("One equilibrium by solving a linear program ") + game); - } - else { - cmd = std::make_shared>(m_doc, useEfg); - cmd->SetCommand(prefix + wxT("logit -e -d 10")); - cmd->SetDescription(wxT("One equilibrium by logit tracing ") + game); - } - } - else if (m_countChoice->GetSelection() == 1) { - if (m_doc->GetGame()->NumPlayers() == 2) { - cmd = std::make_shared>(m_doc, useEfg); - cmd->SetCommand(prefix + wxT("lcp") + options); - cmd->SetDescription(wxT("Some equilibria by solving a linear complementarity program ") + - game); - } - else { - cmd = std::make_shared>(m_doc, false); - cmd->SetCommand(prefix + wxT("simpdiv -d 10 -n 20 -r 100") + options); - cmd->SetDescription(wxT("Some equilibria by simplicial subdivision ") + game); - } - } - else { - if (m_doc->GetGame()->NumPlayers() == 2) { - cmd = std::make_shared>(m_doc, false); - cmd->SetCommand(prefix + wxT("enummixed")); - cmd->SetDescription( - wxT("All equilibria by enumeration of mixed strategies in strategic game")); - } - else { - cmd = std::make_shared>(m_doc, useEfg); - cmd->SetCommand(prefix + wxT("enumpoly -d 10") + options); - cmd->SetDescription(wxT("All equilibria by solving polynomial systems ") + game); - } - } - } - else if (method == s_enumpure) { - cmd = std::make_shared>(m_doc, false); - cmd->SetCommand(prefix + wxT("enumpure") + options); - cmd->SetDescription(count + wxT(" in pure strategies in strategic game")); - } - else if (method == s_enummixed) { - cmd = std::make_shared>(m_doc, false); - cmd->SetCommand(prefix + wxT("enummixed") + options); - cmd->SetDescription(count + wxT(" by enumeration of mixed strategies in strategic game")); - } - else if (method == s_enumpoly) { - cmd = std::make_shared>(m_doc, useEfg); - cmd->SetCommand(prefix + wxT("enumpoly -d 10") + options); - cmd->SetDescription(count + wxT(" by solving polynomial systems ") + game); + wxString count; + switch (computation.target) { + case NashEquilibriumTarget::One: + count = wxT("One equilibrium "); + break; + case NashEquilibriumTarget::Some: + count = wxT("Some equilibria "); + break; + case NashEquilibriumTarget::All: + count = wxT("All equilibria "); + break; } - else if (method == s_gnm) { - cmd = std::make_shared>(m_doc, false); - cmd->SetCommand(prefix + wxT("gnm -d 10") + options); - cmd->SetDescription(count + wxT(" by global Newton tracing in strategic game")); - } - else if (method == s_ipa) { - cmd = std::make_shared>(m_doc, false); - cmd->SetCommand(prefix + wxT("ipa -d 10") + options); - cmd->SetDescription(count + wxT(" by iterated polymatrix approximation in strategic game")); - } - else if (method == s_lp) { - cmd = std::make_shared>(m_doc, useEfg); - cmd->SetCommand(prefix + wxT("lp") + options); - cmd->SetDescription(count + wxT(" by solving a linear program ") + game); - } - else if (method == s_lcp) { - cmd = std::make_shared>(m_doc, useEfg); - cmd->SetCommand(prefix + wxT("lcp") + options); - cmd->SetDescription(count + wxT(" by solving a linear complementarity program ") + game); - } - else if (method == s_liap) { - cmd = std::make_shared>(m_doc, false); - cmd->SetCommand(prefix + wxT("liap -d 10") + options); - cmd->SetDescription(count + wxT(" by function minimization in strategic game")); - } - else if (method == s_logit) { - cmd = std::make_shared>(m_doc, useEfg); - cmd->SetCommand(prefix + wxT("logit -e -d 10") + options); - cmd->SetDescription(count + wxT(" by logit tracing ") + game); - } - else if (method == s_simpdiv) { - cmd = std::make_shared>(m_doc, false); - cmd->SetCommand(prefix + wxT("simpdiv -d 10 -n 20 -r 100") + options); - cmd->SetDescription(count + wxT(" by simplicial subdivision in strategic game")); - } - else { - // Shouldn't happen! - } - - return cmd; + const wxString representation = + useBehavior ? wxT(" in extensive game") : wxT(" in strategic game"); + output->SetComputationSpec(computation); + output->SetCommand(ExternalCommand(computation)); + output->SetDescription(count + MethodDescription(computation.method) + + ParameterDescription(computation.method) + representation); + return output; } } // namespace Gambit::GUI diff --git a/src/gui/dlnash.h b/src/gui/dlnash.h index a4129b37d..ee55b3881 100644 --- a/src/gui/dlnash.h +++ b/src/gui/dlnash.h @@ -33,6 +33,9 @@ class NashChoiceDialog final : public wxDialog { // Event handlers void OnCount(wxCommandEvent &); void OnMethod(wxCommandEvent &); + void UpdateRepresentationChoice(); + + NashComputationSpec GetComputation() const; public: NashChoiceDialog(wxWindow *, GameDocument *); diff --git a/src/gui/nashspec.h b/src/gui/nashspec.h new file mode 100644 index 000000000..bbf7423a0 --- /dev/null +++ b/src/gui/nashspec.h @@ -0,0 +1,95 @@ +// +// This file is part of Gambit +// Copyright (c) 1994-2026, The Gambit Project (https://www.gambit-project.org) +// +// Typed specifications for Nash equilibrium computations initiated by the GUI. +// +// 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_GUI_NASHSPEC_H +#define GAMBIT_GUI_NASHSPEC_H + +#include + +#include "core/rational.h" + +namespace Gambit::GUI { + +enum class NashRepresentation { Strategic, Behavior }; +enum class NashEquilibriumTarget { One, Some, All }; + +struct EnumPureNashSpec {}; +struct EnumMixedNashSpec {}; + +struct EnumPolyNashSpec { + int stopAfter{0}; + double maxRegret{1.0e-4}; +}; + +struct GNMNashSpec { + int perturbations{1}; + double lambdaEnd{-10.0}; + int steps{100}; + int localNewtonInterval{3}; + int localNewtonMaxIterations{10}; +}; + +struct IPANashSpec { + int perturbations{1}; +}; + +struct LPNashSpec {}; + +struct LCPNashSpec { + int stopAfter{0}; + int maxDepth{0}; +}; + +struct LiapNashSpec { + int startingPoints{10}; + double maxRegret{1.0e-4}; + int maxIterations{1000}; +}; + +struct LogitNashSpec { + double maxRegret{1.0e-8}; + double omega{1.0}; + double firstStep{0.03}; + double maxAcceleration{1.1}; +}; + +struct SimpdivNashSpec { + int startingPoints{20}; + int randomDenominator{100}; + int gridResize{2}; + int leashLength{0}; + Rational maxRegret{1, 10000000}; +}; + +using NashMethodSpec = + std::variant; + +struct NashComputationSpec { + NashRepresentation representation; + NashEquilibriumTarget target; + NashMethodSpec method; + bool selectedByRecommendation{false}; +}; + +} // namespace Gambit::GUI + +#endif // GAMBIT_GUI_NASHSPEC_H