Skip to content

Commit 45fb000

Browse files
Merge branch 'master' into catalog/744
2 parents bc17cb0 + d1b8dca commit 45fb000

20 files changed

+222
-168
lines changed

ChangeLog

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
- `enumpoly` would take a very long time on some supports where an equilibrium is located on the
1010
boundary of the projected game. Search is now restricted to the interior of the space ruling
1111
these out; these will always be found by another projection. (#756)
12+
- In the graphical interface, the logit correspondence display would fail and terminate the program
13+
on very small (<10^{-300}) probabilities.
1214

1315
## [16.5.1] - unreleased
1416

src/games/game.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -593,6 +593,21 @@ inline GameNodeRep::Actions::iterator::iterator(GameInfosetRep::Actions::iterato
593593

594594
inline GameNode GameNodeRep::Actions::iterator::GetOwner() const { return m_child_it.GetOwner(); }
595595

596+
inline void ValidateDistribution(const Array<Number> &p_probs, const bool p_normalized = true)
597+
{
598+
if (std::any_of(p_probs.begin(), p_probs.end(),
599+
[](const Number &x) { return static_cast<Rational>(x) < Rational(0); })) {
600+
throw ValueException("Probabilities must be non-negative numbers");
601+
}
602+
if (!p_normalized) {
603+
return;
604+
}
605+
if (sum_function(p_probs, [](const Number &n) { return static_cast<Rational>(n); }) !=
606+
Rational(1)) {
607+
throw ValueException("Probabilities must sum to exactly one");
608+
}
609+
}
610+
596611
enum class TraversalOrder { Preorder, Postorder };
597612

598613
class CartesianProductSpace {

src/games/gametree.cc

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1324,17 +1324,8 @@ Game GameTreeRep::SetChanceProbs(const GameInfoset &p_infoset, const Array<Numbe
13241324
if (p_infoset->m_actions.size() != p_probs.size()) {
13251325
throw DimensionException("The number of probabilities given must match the number of actions");
13261326
}
1327+
ValidateDistribution(p_probs);
13271328
IncrementVersion();
1328-
if (std::any_of(p_probs.begin(), p_probs.end(),
1329-
[](const Number &x) { return static_cast<Rational>(x) < Rational(0); })) {
1330-
throw ValueException("Probabilities must be non-negative numbers");
1331-
}
1332-
auto sum = std::accumulate(
1333-
p_probs.begin(), p_probs.end(), Rational(0),
1334-
[](const Rational &r, const Number &n) { return r + static_cast<Rational>(n); });
1335-
if (sum != Rational(1)) {
1336-
throw ValueException("Probabilities must sum to exactly one");
1337-
}
13381329
std::copy(p_probs.begin(), p_probs.end(), p_infoset->m_probs.begin());
13391330
ClearComputedValues();
13401331
return shared_from_this();

src/gui/dleditmove.cc

Lines changed: 39 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,11 @@
3232
#include "renratio.h"
3333

3434
namespace Gambit::GUI {
35+
3536
class ActionSheet final : public wxSheet {
36-
GameInfoset m_infoset;
37+
GameInfoset m_infoset{nullptr};
38+
wxSheetCellAttr m_labelAttr;
39+
wxFont m_labelFont, m_cellFont;
3740

3841
// Overriding wxSheet members
3942
wxSheetCellAttr GetAttr(const wxSheetCoords &p_coords, wxSheetAttr_Type) const override;
@@ -48,8 +51,17 @@ class ActionSheet final : public wxSheet {
4851
};
4952

5053
ActionSheet::ActionSheet(wxWindow *p_parent, const GameInfoset &p_infoset)
51-
: wxSheet(p_parent, wxID_ANY), m_infoset(p_infoset)
54+
: wxSheet(p_parent, wxID_ANY), m_infoset(p_infoset), m_labelFont(GetFont()),
55+
m_cellFont(GetFont())
5256
{
57+
m_labelFont.MakeBold();
58+
59+
m_labelAttr = GetSheetRefData()->m_defaultRowLabelAttr;
60+
m_labelAttr.SetFont(m_labelFont);
61+
m_labelAttr.SetAlignment(wxALIGN_CENTER, wxALIGN_CENTER);
62+
m_labelAttr.SetOrientation(wxHORIZONTAL);
63+
m_labelAttr.SetReadOnly(true);
64+
5365
CreateGrid(p_infoset->GetActions().size(), (p_infoset->IsChanceInfoset()) ? 2 : 1);
5466
SetRowLabelWidth(40);
5567
SetColLabelHeight(25);
@@ -103,29 +115,12 @@ Array<Number> ActionSheet::GetActionProbs()
103115

104116
wxSheetCellAttr ActionSheet::GetAttr(const wxSheetCoords &p_coords, wxSheetAttr_Type) const
105117
{
106-
if (IsRowLabelCell(p_coords)) {
107-
wxSheetCellAttr attr(GetSheetRefData()->m_defaultRowLabelAttr);
108-
attr.SetFont(wxFont(10, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD));
109-
attr.SetAlignment(wxALIGN_CENTER, wxALIGN_CENTER);
110-
attr.SetOrientation(wxHORIZONTAL);
111-
attr.SetReadOnly(true);
112-
return attr;
113-
}
114-
else if (IsColLabelCell(p_coords)) {
115-
wxSheetCellAttr attr(GetSheetRefData()->m_defaultColLabelAttr);
116-
attr.SetFont(wxFont(10, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD));
117-
attr.SetAlignment(wxALIGN_CENTER, wxALIGN_CENTER);
118-
attr.SetOrientation(wxHORIZONTAL);
119-
attr.SetReadOnly(true);
120-
return attr;
121-
}
122-
else if (IsCornerLabelCell(p_coords)) {
123-
const wxSheetCellAttr attr(GetSheetRefData()->m_defaultCornerLabelAttr);
124-
return attr;
118+
if (IsLabelCell(p_coords)) {
119+
return m_labelAttr;
125120
}
126121

127122
wxSheetCellAttr attr(GetSheetRefData()->m_defaultGridCellAttr);
128-
attr.SetFont(wxFont(10, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL));
123+
attr.SetFont(m_cellFont);
129124
attr.SetAlignment(wxALIGN_CENTER, wxALIGN_CENTER);
130125
attr.SetOrientation(wxHORIZONTAL);
131126
attr.SetReadOnly(false);
@@ -143,9 +138,7 @@ wxSheetCellAttr ActionSheet::GetAttr(const wxSheetCoords &p_coords, wxSheetAttr_
143138
// class EditMoveDialog
144139
//======================================================================
145140

146-
wxBEGIN_EVENT_TABLE(EditMoveDialog, wxDialog) EVT_BUTTON(wxID_OK, EditMoveDialog::OnOK)
147-
wxEND_EVENT_TABLE() EditMoveDialog::EditMoveDialog(wxWindow *p_parent,
148-
const GameInfoset &p_infoset)
141+
EditMoveDialog::EditMoveDialog(wxWindow *p_parent, const GameInfoset &p_infoset)
149142
: wxDialog(p_parent, wxID_ANY, _("Move properties"), wxDefaultPosition), m_infoset(p_infoset)
150143
{
151144
auto *topSizer = new wxBoxSizer(wxVERTICAL);
@@ -158,10 +151,11 @@ wxBEGIN_EVENT_TABLE(EditMoveDialog, wxDialog) EVT_BUTTON(wxID_OK, EditMoveDialog
158151
labelSizer->Add(m_infosetName, 1, wxALL | wxEXPAND, 5);
159152
topSizer->Add(labelSizer, 0, wxALL | wxEXPAND, 0);
160153

161-
topSizer->Add(new wxStaticText(
162-
this, wxID_STATIC,
163-
wxString::Format(_("Number of members: %d"), p_infoset->GetMembers().size())),
164-
0, wxALL | wxALIGN_CENTER, 5);
154+
{
155+
wxString label;
156+
label << _("Number of members: ") << p_infoset->GetMembers().size();
157+
topSizer->Add(new wxStaticText(this, wxID_STATIC, label), 0, wxALL | wxALIGN_CENTER, 5);
158+
}
165159

166160
auto *playerSizer = new wxBoxSizer(wxHORIZONTAL);
167161
playerSizer->Add(new wxStaticText(this, wxID_STATIC, _("Belongs to player")), 0,
@@ -170,11 +164,13 @@ wxBEGIN_EVENT_TABLE(EditMoveDialog, wxDialog) EVT_BUTTON(wxID_OK, EditMoveDialog
170164
if (p_infoset->IsChanceInfoset()) {
171165
m_player->Append(_("Chance"));
172166
m_player->SetSelection(0);
167+
m_player->Disable();
173168
}
174169
else {
175170
for (const auto &player : p_infoset->GetGame()->GetPlayers()) {
176-
m_player->Append(wxString::Format(_T("%d: "), player->GetNumber()) +
177-
wxString(player->GetLabel().c_str(), *wxConvCurrent));
171+
wxString label;
172+
label << player->GetNumber() << ": " << player->GetLabel();
173+
m_player->Append(label);
178174
}
179175
m_player->SetSelection(p_infoset->GetPlayer()->GetNumber() - 1);
180176
}
@@ -187,18 +183,13 @@ wxBEGIN_EVENT_TABLE(EditMoveDialog, wxDialog) EVT_BUTTON(wxID_OK, EditMoveDialog
187183
actionBoxSizer->Add(m_actionSheet, 1, wxALL | wxEXPAND, 5);
188184
topSizer->Add(actionBoxSizer, 0, wxALL | wxEXPAND, 5);
189185

190-
auto *buttonSizer = new wxBoxSizer(wxHORIZONTAL);
191-
buttonSizer->Add(new wxButton(this, wxID_CANCEL, _("Cancel")), 0, wxALL, 5);
192-
auto *okButton = new wxButton(this, wxID_OK, _("OK"));
193-
okButton->SetDefault();
194-
buttonSizer->Add(okButton, 0, wxALL, 5);
195-
topSizer->Add(buttonSizer, 0, wxALL | wxALIGN_RIGHT, 5);
196-
197-
SetSizer(topSizer);
198-
topSizer->Fit(this);
199-
topSizer->SetSizeHints(this);
200-
wxTopLevelWindowBase::Layout();
186+
if (auto *buttons = CreateSeparatedButtonSizer(wxOK | wxCANCEL)) {
187+
topSizer->Add(buttons, 0, wxALL | wxEXPAND, 5);
188+
}
189+
SetSizerAndFit(topSizer);
201190
CenterOnParent();
191+
192+
Bind(wxEVT_BUTTON, &EditMoveDialog::OnOK, this, wxID_OK);
202193
}
203194

204195
void EditMoveDialog::OnOK(wxCommandEvent &p_event)
@@ -207,23 +198,16 @@ void EditMoveDialog::OnOK(wxCommandEvent &p_event)
207198
p_event.Skip();
208199
return;
209200
}
210-
auto probs = m_actionSheet->GetActionProbs();
211-
if (std::any_of(probs.begin(), probs.end(),
212-
[](const Number &p) { return static_cast<Rational>(p) < Rational(0); })) {
213-
wxMessageBox("Action probabilities must be non-negative numbers.", "Error");
214-
return;
215-
}
216-
if (std::accumulate(probs.begin(), probs.end(), Rational(0),
217-
[](const Rational &s, const Number &p) {
218-
return s + static_cast<Rational>(p);
219-
}) != Rational(1)) {
220-
p_event.Skip();
201+
try {
202+
ValidateDistribution(m_actionSheet->GetActionProbs());
221203
}
222-
else {
223-
wxRichMessageDialog(this, "Action probabilities must sum to exactly one", "Error",
204+
catch (ValueException &) {
205+
wxRichMessageDialog(this, "Probabilities must be nonnegative numbers summing to one.", "Error",
224206
wxOK | wxCENTRE | wxICON_ERROR)
225207
.ShowModal();
208+
return;
226209
}
210+
p_event.Skip();
227211
}
228212

229213
int EditMoveDialog::NumActions() const { return m_actionSheet->NumActions(); }

src/gui/dleditmove.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,6 @@ class EditMoveDialog final : public wxDialog {
4545
int NumActions() const;
4646
wxString GetActionName(int p_act) const;
4747
Array<Number> GetActionProbs() const;
48-
49-
wxDECLARE_EVENT_TABLE();
5048
};
5149
} // namespace Gambit::GUI
5250

src/gui/dleditnode.cc

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,9 @@ EditNodeDialog::EditNodeDialog(wxWindow *p_parent, const GameNode &p_node)
5555
for (const auto &infoset : p_node->GetGame()->GetChance()->GetInfosets()) {
5656
if (infoset->GetActions().size() == p_node->GetChildren().size()) {
5757
m_infosetList.push_back(infoset);
58-
m_infoset->Append(wxString::Format(_("Chance infoset %d"), infoset->GetNumber()));
58+
wxString label;
59+
label << _("Chance infoset ") << infoset->GetNumber();
60+
m_infoset->Append(label);
5961
if (infoset == p_node->GetInfoset()) {
6062
selection = m_infosetList.size();
6163
}
@@ -69,8 +71,10 @@ EditNodeDialog::EditNodeDialog(wxWindow *p_parent, const GameNode &p_node)
6971
for (const auto &infoset : player->GetInfosets()) {
7072
if (infoset->GetActions().size() == p_node->GetChildren().size()) {
7173
m_infosetList.push_back(infoset);
72-
m_infoset->Append(wxString::Format(_("Player %d, Infoset %d"), player->GetNumber(),
73-
infoset->GetNumber()));
74+
wxString label;
75+
label << _("Player ") << player->GetNumber() << _(", Infoset ")
76+
<< infoset->GetNumber();
77+
m_infoset->Append(label);
7478
if (infoset == p_node->GetInfoset()) {
7579
selection = m_infosetList.size();
7680
}

src/gui/dlefglayout.cc

Lines changed: 40 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -100,24 +100,28 @@ LayoutNodesPanel::LayoutNodesPanel(wxWindow *p_parent, const TreeRenderConfig &p
100100
gridSizer->Add(new wxStaticText(this, wxID_ANY, _("Horizontal size of nodes")), 0,
101101
wxALIGN_CENTER_VERTICAL | wxALL, 5);
102102

103-
constexpr int NODE_LENGTH_MIN = 5;
104-
constexpr int NODE_LENGTH_MAX = 100;
105-
106-
m_nodeSize = new wxSpinCtrl(this, wxID_ANY, wxString::Format(_T("%d"), p_settings.GetNodeSize()),
107-
wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, NODE_LENGTH_MIN,
108-
NODE_LENGTH_MAX);
109-
gridSizer->Add(m_nodeSize, 1, wxEXPAND | wxALL, 5);
103+
{
104+
constexpr int NODE_LENGTH_MIN = 5;
105+
constexpr int NODE_LENGTH_MAX = 100;
106+
wxString label;
107+
label << p_settings.GetNodeSize();
108+
m_nodeSize = new wxSpinCtrl(this, wxID_ANY, label, wxDefaultPosition, wxDefaultSize,
109+
wxSP_ARROW_KEYS, NODE_LENGTH_MIN, NODE_LENGTH_MAX);
110+
gridSizer->Add(m_nodeSize, 1, wxEXPAND | wxALL, 5);
111+
}
110112

111113
gridSizer->Add(new wxStaticText(this, wxID_ANY, _("Vertical spacing between terminal nodes")), 0,
112114
wxALIGN_CENTER_VERTICAL | wxALL, 5);
113115

114-
constexpr int Y_SPACING_MIN = 15;
115-
constexpr int Y_SPACING_MAX = 60;
116-
117-
m_terminalSpacing = new wxSpinCtrl(
118-
this, wxID_ANY, wxString::Format(_T("%d"), p_settings.GetTerminalSpacing()),
119-
wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, Y_SPACING_MIN, Y_SPACING_MAX);
120-
gridSizer->Add(m_terminalSpacing, 1, wxEXPAND | wxALL, 5);
116+
{
117+
constexpr int Y_SPACING_MIN = 15;
118+
constexpr int Y_SPACING_MAX = 60;
119+
wxString label;
120+
label << p_settings.GetTerminalSpacing();
121+
m_terminalSpacing = new wxSpinCtrl(this, wxID_ANY, label, wxDefaultPosition, wxDefaultSize,
122+
wxSP_ARROW_KEYS, Y_SPACING_MIN, Y_SPACING_MAX);
123+
gridSizer->Add(m_terminalSpacing, 1, wxEXPAND | wxALL, 5);
124+
}
121125

122126
sizeSizer->Add(gridSizer, 1, wxALL | wxEXPAND, 5);
123127
topSizer->Add(sizeSizer, 0, wxALL | wxALIGN_CENTER, 5);
@@ -151,12 +155,6 @@ class LayoutBranchesPanel : public wxPanel {
151155
LayoutBranchesPanel::LayoutBranchesPanel(wxWindow *p_parent, const TreeRenderConfig &p_settings)
152156
: wxPanel(p_parent, wxID_ANY)
153157
{
154-
constexpr int BRANCH_LENGTH_MIN = 0;
155-
constexpr int BRANCH_LENGTH_MAX = 100;
156-
157-
constexpr int TINE_LENGTH_MIN = 20;
158-
constexpr int TINE_LENGTH_MAX = 100;
159-
160158
auto *topSizer = new wxBoxSizer(wxVERTICAL);
161159

162160
auto *styleBoxSizer = new wxStaticBoxSizer(wxHORIZONTAL, this, _("Drawing branches"));
@@ -186,19 +184,29 @@ LayoutBranchesPanel::LayoutBranchesPanel(wxWindow *p_parent, const TreeRenderCon
186184
auto *gridSizer = new wxFlexGridSizer(2);
187185
gridSizer->AddGrowableCol(1);
188186

189-
gridSizer->Add(new wxStaticText(this, wxID_ANY, _("size of branch fork")), 0,
190-
wxALIGN_CENTER_VERTICAL | wxALL, 5);
191-
m_branchLength = new wxSpinCtrl(
192-
this, wxID_ANY, wxString::Format(_T("%d"), p_settings.GetBranchLength()), wxDefaultPosition,
193-
wxDefaultSize, wxSP_ARROW_KEYS, BRANCH_LENGTH_MIN, BRANCH_LENGTH_MAX);
194-
gridSizer->Add(m_branchLength, 1, wxALL | wxEXPAND, 5);
187+
{
188+
constexpr int BRANCH_LENGTH_MIN = 0;
189+
constexpr int BRANCH_LENGTH_MAX = 100;
190+
gridSizer->Add(new wxStaticText(this, wxID_ANY, _("size of branch fork")), 0,
191+
wxALIGN_CENTER_VERTICAL | wxALL, 5);
192+
wxString label;
193+
label << p_settings.GetBranchLength();
194+
m_branchLength = new wxSpinCtrl(this, wxID_ANY, label, wxDefaultPosition, wxDefaultSize,
195+
wxSP_ARROW_KEYS, BRANCH_LENGTH_MIN, BRANCH_LENGTH_MAX);
196+
gridSizer->Add(m_branchLength, 1, wxALL | wxEXPAND, 5);
197+
}
195198

196-
gridSizer->Add(new wxStaticText(this, wxID_ANY, _("size of branch tine")), 1,
197-
wxALIGN_CENTER_VERTICAL | wxALL, 5);
198-
m_tineLength = new wxSpinCtrl(
199-
this, wxID_ANY, wxString::Format(_T("%d"), p_settings.GetTineLength()), wxDefaultPosition,
200-
wxDefaultSize, wxSP_ARROW_KEYS, TINE_LENGTH_MIN, TINE_LENGTH_MAX);
201-
gridSizer->Add(m_tineLength, 1, wxALL | wxEXPAND, 5);
199+
{
200+
constexpr int TINE_LENGTH_MIN = 20;
201+
constexpr int TINE_LENGTH_MAX = 100;
202+
gridSizer->Add(new wxStaticText(this, wxID_ANY, _("size of branch tine")), 1,
203+
wxALIGN_CENTER_VERTICAL | wxALL, 5);
204+
wxString label;
205+
label << p_settings.GetTineLength();
206+
m_tineLength = new wxSpinCtrl(this, wxID_ANY, label, wxDefaultPosition, wxDefaultSize,
207+
wxSP_ARROW_KEYS, TINE_LENGTH_MIN, TINE_LENGTH_MAX);
208+
gridSizer->Add(m_tineLength, 1, wxALL | wxEXPAND, 5);
209+
}
202210

203211
lengthSizer->Add(gridSizer, 1, wxALL | wxEXPAND, 5);
204212
topSizer->Add(lengthSizer, 0, wxALL | wxALIGN_CENTER, 5);

src/gui/dlefglogit.cc

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,15 +74,18 @@ LogitBehavList::LogitBehavList(wxWindow *p_parent, GameDocument *p_doc)
7474
wxString LogitBehavList::GetCellValue(const wxSheetCoords &p_coords)
7575
{
7676
if (IsRowLabelCell(p_coords)) {
77-
return wxString::Format(wxT("%d"), p_coords.GetRow() + 1);
77+
wxString label;
78+
label << (p_coords.GetRow() + 1);
79+
return label;
7880
}
7981
if (IsColLabelCell(p_coords)) {
8082
if (p_coords.GetCol() == 0) {
8183
return wxT("Lambda");
8284
}
8385
const GameAction action = m_doc->GetAction(p_coords.GetCol());
84-
return (wxString::Format(wxT("%d: "), action->GetInfoset()->GetNumber()) +
85-
wxString(action->GetLabel().c_str(), *wxConvCurrent));
86+
wxString label;
87+
label << action->GetInfoset()->GetNumber() << ": " << action->GetLabel();
88+
return label;
8689
}
8790
if (IsCornerLabelCell(p_coords)) {
8891
return wxT("#");
@@ -171,7 +174,12 @@ void LogitBehavList::AddProfile(const wxString &p_text, bool p_forceShow)
171174
}
172175
m_lambdas.push_back(std::stod(next.ToStdString()));
173176
for (size_t i = 1; i <= profile->BehaviorProfileLength(); i++) {
174-
(*profile)[i] = std::stod(tok.GetNextToken().ToStdString());
177+
try {
178+
(*profile)[i] = std::stod(tok.GetNextToken().ToStdString());
179+
}
180+
catch (std::out_of_range &) {
181+
(*profile)[i] = 0.0;
182+
}
175183
}
176184
m_profiles.push_back(profile);
177185
if (p_forceShow || m_profiles.size() - GetNumberRows() > 20) {

src/gui/dlefgreveal.cc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,10 +71,10 @@ RevealMoveDialog::RevealMoveDialog(wxWindow *p_parent, const Game &p_game)
7171
for (const auto &player : players) {
7272
wxString label;
7373
if (!player->GetLabel().empty()) {
74-
label = wxString::FromUTF8(player->GetLabel());
74+
label << player->GetLabel();
7575
}
7676
else {
77-
label = wxString::Format("Player %u", player->GetNumber());
77+
label << "Player " << player->GetNumber();
7878
}
7979
auto *cb = new wxCheckBox(this, wxID_ANY, label);
8080
cb->SetValue(true);

0 commit comments

Comments
 (0)