Skip to content

Commit e5d7523

Browse files
authored
Merge pull request #3379 from stan-dev/feat/optimization-log-convergence-output
Add convergence information to optimization outputs
2 parents 4e3fb8e + c909bea commit e5d7523

9 files changed

Lines changed: 81 additions & 54 deletions

File tree

src/stan/services/optimize/bfgs.hpp

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -83,13 +83,15 @@ int bfgs(Model& model, const stan::io::var_context& init,
8383
bfgs._conv_opts.maxIts = num_iterations;
8484

8585
double lp = bfgs.logp();
86+
int ret = 0;
8687

8788
std::stringstream initial_msg;
8889
initial_msg << "Initial log joint probability = " << lp;
8990
logger.info(initial_msg);
9091

9192
std::vector<std::string> names;
9293
names.push_back("lp__");
94+
names.push_back("converged__");
9395
model.constrained_param_names(names, true, true);
9496
parameter_writer(names);
9597

@@ -109,10 +111,9 @@ int bfgs(Model& model, const stan::io::var_context& init,
109111
if (msg.str().length() > 0)
110112
logger.info(msg);
111113

112-
values.insert(values.begin(), lp);
114+
values.insert(values.begin(), {lp, static_cast<double>(ret)});
113115
parameter_writer(values);
114116
}
115-
int ret = 0;
116117

117118
try {
118119
while (ret == 0) {
@@ -168,7 +169,7 @@ int bfgs(Model& model, const stan::io::var_context& init,
168169
if (msg.str().length() > 0)
169170
logger.info(msg);
170171

171-
values.insert(values.begin(), lp);
172+
values.insert(values.begin(), {lp, static_cast<double>(ret)});
172173
parameter_writer(values);
173174
}
174175
}
@@ -192,7 +193,7 @@ int bfgs(Model& model, const stan::io::var_context& init,
192193
}
193194
if (msg.str().length() > 0)
194195
logger.info(msg);
195-
values.insert(values.begin(), lp);
196+
values.insert(values.begin(), {lp, static_cast<double>(ret)});
196197
parameter_writer(values);
197198
}
198199

@@ -201,10 +202,12 @@ int bfgs(Model& model, const stan::io::var_context& init,
201202
if (ret >= 0) {
202203
logger.info("Optimization terminated normally: ");
203204
logger.info(" " + error_string);
205+
parameter_writer("Optimization terminated normally: " + error_string);
204206
return_code = error_codes::OK;
205207
} else {
206208
logger.error("Optimization terminated with error: ");
207209
logger.error(" " + error_string);
210+
parameter_writer("Optimization terminated with error: " + error_string);
208211
return_code = error_codes::SOFTWARE;
209212
}
210213

src/stan/services/optimize/lbfgs.hpp

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,13 +87,15 @@ int lbfgs(Model& model, const stan::io::var_context& init,
8787
lbfgs._conv_opts.maxIts = num_iterations;
8888

8989
double lp = lbfgs.logp();
90+
int ret = 0;
9091

9192
std::stringstream initial_msg;
9293
initial_msg << "Initial log joint probability = " << lp;
9394
logger.info(initial_msg);
9495

9596
std::vector<std::string> names;
9697
names.push_back("lp__");
98+
names.push_back("converged__");
9799
model.constrained_param_names(names, true, true);
98100
parameter_writer(names);
99101

@@ -104,10 +106,9 @@ int lbfgs(Model& model, const stan::io::var_context& init,
104106
if (msg.str().length() > 0)
105107
logger.info(msg);
106108

107-
values.insert(values.begin(), lp);
109+
values.insert(values.begin(), {lp, static_cast<double>(ret)});
108110
parameter_writer(values);
109111
}
110-
int ret = 0;
111112

112113
try {
113114
while (ret == 0) {
@@ -161,7 +162,7 @@ int lbfgs(Model& model, const stan::io::var_context& init,
161162
if (msg.str().length() > 0)
162163
logger.info(msg);
163164

164-
values.insert(values.begin(), lp);
165+
values.insert(values.begin(), {lp, static_cast<double>(ret)});
165166
parameter_writer(values);
166167
}
167168
}
@@ -186,7 +187,7 @@ int lbfgs(Model& model, const stan::io::var_context& init,
186187
if (msg.str().length() > 0)
187188
logger.info(msg);
188189

189-
values.insert(values.begin(), lp);
190+
values.insert(values.begin(), {lp, static_cast<double>(ret)});
190191
parameter_writer(values);
191192
}
192193

@@ -196,10 +197,12 @@ int lbfgs(Model& model, const stan::io::var_context& init,
196197
if (ret >= 0) {
197198
logger.info("Optimization terminated normally: ");
198199
logger.info(" " + error_string);
200+
parameter_writer("Optimization terminated normally: " + error_string);
199201
return_code = error_codes::OK;
200202
} else {
201203
logger.error("Optimization terminated with error: ");
202204
logger.error(" " + error_string);
205+
parameter_writer("Optimization terminated with error: " + error_string);
203206
return_code = error_codes::SOFTWARE;
204207
}
205208

src/stan/services/optimize/newton.hpp

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include <stan/callbacks/logger.hpp>
66
#include <stan/callbacks/writer.hpp>
77
#include <stan/io/var_context.hpp>
8+
#include <stan/optimization/bfgs.hpp>
89
#include <stan/optimization/newton.hpp>
910
#include <stan/services/error_codes.hpp>
1011
#include <stan/services/util/initialize.hpp>
@@ -85,19 +86,23 @@ int newton(Model& model, const stan::io::var_context& init,
8586
logger.info(msg);
8687

8788
std::vector<std::string> names;
89+
8890
names.push_back("lp__");
91+
names.push_back("converged__");
8992
model.constrained_param_names(names, true, true);
9093
parameter_writer(names);
9194

9295
double lastlp = lp;
96+
int ret = 0;
97+
9398
for (int m = 0; m < num_iterations; m++) {
9499
if (save_iterations) {
95100
std::vector<double> values;
96101
std::stringstream ss;
97102
model.write_array(rng, cont_vector, disc_vector, values, true, true, &ss);
98103
if (ss.str().length() > 0)
99104
logger.info(ss);
100-
values.insert(values.begin(), lp);
105+
values.insert(values.begin(), {lp, static_cast<double>(ret)});
101106
parameter_writer(values);
102107
}
103108
interrupt();
@@ -115,13 +120,19 @@ int newton(Model& model, const stan::io::var_context& init,
115120
break;
116121
}
117122

123+
if (std::fabs(lp - lastlp) <= 1e-8) {
124+
ret = optimization::TERM_ABSF;
125+
} else {
126+
ret = optimization::TERM_MAXIT;
127+
}
128+
118129
{
119130
std::vector<double> values;
120131
std::stringstream ss;
121132
model.write_array(rng, cont_vector, disc_vector, values, true, true, &ss);
122133
if (ss.str().length() > 0)
123134
logger.info(ss);
124-
values.insert(values.begin(), lp);
135+
values.insert(values.begin(), {lp, static_cast<double>(ret)});
125136
parameter_writer(values);
126137
}
127138
return error_codes::OK;

src/test/unit/services/optimize/bfgs_jacobian_test.cpp

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,11 @@ TEST_F(ServicesOptimize, withJacobian) {
3434
EXPECT_TRUE(logger.find("Optimization terminated normally: "));
3535
EXPECT_FLOAT_EQ(return_code, 0);
3636

37-
ASSERT_EQ(2, parameter.names_.size());
37+
ASSERT_EQ(3, parameter.names_.size());
3838
EXPECT_EQ("lp__", parameter.names_[0]);
39-
EXPECT_EQ("sigma", parameter.names_[1]);
40-
EXPECT_NEAR((3 + std::sqrt(13)) / 2, parameter.states_.back()[1], 0.0001);
39+
EXPECT_EQ("converged__", parameter.names_[1]);
40+
EXPECT_EQ("sigma", parameter.names_[2]);
41+
EXPECT_NEAR((3 + std::sqrt(13)) / 2, parameter.states_.back()[2], 0.0001);
4142
EXPECT_GT(interrupt.call_count(), 0);
4243
}
4344

@@ -58,9 +59,10 @@ TEST_F(ServicesOptimize, withoutJacobian) {
5859
EXPECT_TRUE(logger.find("Optimization terminated normally: "));
5960
EXPECT_FLOAT_EQ(return_code, 0);
6061

61-
ASSERT_EQ(2, parameter.names_.size());
62+
ASSERT_EQ(3, parameter.names_.size());
6263
EXPECT_EQ("lp__", parameter.names_[0]);
63-
EXPECT_EQ("sigma", parameter.names_[1]);
64-
EXPECT_NEAR(3, parameter.states_.back()[1], 0.0001);
64+
EXPECT_EQ("converged__", parameter.names_[1]);
65+
EXPECT_EQ("sigma", parameter.names_[2]);
66+
EXPECT_NEAR(3, parameter.states_.back()[2], 0.0001);
6567
EXPECT_GT(interrupt.call_count(), 1);
6668
}

src/test/unit/services/optimize/bfgs_test.cpp

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,20 +40,21 @@ TEST_F(ServicesOptimize, rosenbrock) {
4040

4141
EXPECT_EQ("0,0\n", init_ss.str());
4242

43-
ASSERT_EQ(3, parameter.names_.size());
43+
ASSERT_EQ(4, parameter.names_.size());
4444
EXPECT_EQ("lp__", parameter.names_[0]);
45-
EXPECT_EQ("x", parameter.names_[1]);
46-
EXPECT_EQ("y", parameter.names_[2]);
45+
EXPECT_EQ("converged__", parameter.names_[1]);
46+
EXPECT_EQ("x", parameter.names_[2]);
47+
EXPECT_EQ("y", parameter.names_[3]);
4748

4849
EXPECT_EQ(20, parameter.states_.size());
49-
EXPECT_FLOAT_EQ(0, parameter.states_.front()[1])
50-
<< "initial value should be (0, 0)";
5150
EXPECT_FLOAT_EQ(0, parameter.states_.front()[2])
5251
<< "initial value should be (0, 0)";
53-
EXPECT_FLOAT_EQ(1, parameter.states_.back()[1])
54-
<< "optimal value should be (1, 1)";
52+
EXPECT_FLOAT_EQ(0, parameter.states_.front()[3])
53+
<< "initial value should be (0, 0)";
5554
EXPECT_FLOAT_EQ(1, parameter.states_.back()[2])
5655
<< "optimal value should be (1, 1)";
56+
EXPECT_FLOAT_EQ(1, parameter.states_.back()[3])
57+
<< "optimal value should be (1, 1)";
5758
EXPECT_FLOAT_EQ(return_code, 0);
5859
EXPECT_EQ(19, interrupt.call_count());
5960
}

src/test/unit/services/optimize/lbfgs_jacobian_test.cpp

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,11 @@ TEST_F(ServicesOptimize, with_jacobian) {
3535
EXPECT_TRUE(logger.find("Optimization terminated normally: "));
3636
EXPECT_FLOAT_EQ(return_code, 0);
3737

38-
ASSERT_EQ(2, parameter.names_.size());
38+
ASSERT_EQ(3, parameter.names_.size());
3939
EXPECT_EQ("lp__", parameter.names_[0]);
40-
EXPECT_EQ("sigma", parameter.names_[1]);
41-
EXPECT_NEAR((3 + std::sqrt(13)) / 2, parameter.states_.back()[1], 0.0001);
40+
EXPECT_EQ("converged__", parameter.names_[1]);
41+
EXPECT_EQ("sigma", parameter.names_[2]);
42+
EXPECT_NEAR((3 + std::sqrt(13)) / 2, parameter.states_.back()[2], 0.0001);
4243
}
4344

4445
TEST_F(ServicesOptimize, without_jacobian) {
@@ -58,8 +59,9 @@ TEST_F(ServicesOptimize, without_jacobian) {
5859
EXPECT_TRUE(logger.find("Optimization terminated normally: "));
5960
EXPECT_FLOAT_EQ(return_code, 0);
6061

61-
ASSERT_EQ(2, parameter.names_.size());
62+
ASSERT_EQ(3, parameter.names_.size());
6263
EXPECT_EQ("lp__", parameter.names_[0]);
63-
EXPECT_EQ("sigma", parameter.names_[1]);
64-
EXPECT_NEAR(3, parameter.states_.back()[1], 0.0001);
64+
EXPECT_EQ("converged__", parameter.names_[1]);
65+
EXPECT_EQ("sigma", parameter.names_[2]);
66+
EXPECT_NEAR(3, parameter.states_.back()[2], 0.0001);
6567
}

src/test/unit/services/optimize/lbfgs_test.cpp

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,19 +40,20 @@ TEST_F(ServicesOptimize, rosenbrock) {
4040

4141
EXPECT_EQ("0,0\n", init_ss.str());
4242

43-
ASSERT_EQ(3, parameter.names_.size());
43+
ASSERT_EQ(4, parameter.names_.size());
4444
EXPECT_EQ("lp__", parameter.names_[0]);
45-
EXPECT_EQ("x", parameter.names_[1]);
46-
EXPECT_EQ("y", parameter.names_[2]);
45+
EXPECT_EQ("converged__", parameter.names_[1]);
46+
EXPECT_EQ("x", parameter.names_[2]);
47+
EXPECT_EQ("y", parameter.names_[3]);
4748

4849
EXPECT_EQ(23, parameter.states_.size());
49-
EXPECT_FLOAT_EQ(0, parameter.states_.front()[1])
50-
<< "initial value should be (0, 0)";
5150
EXPECT_FLOAT_EQ(0, parameter.states_.front()[2])
5251
<< "initial value should be (0, 0)";
53-
EXPECT_FLOAT_EQ(0.99998301, parameter.states_.back()[1])
52+
EXPECT_FLOAT_EQ(0, parameter.states_.front()[3])
53+
<< "initial value should be (0, 0)";
54+
EXPECT_FLOAT_EQ(0.99998301, parameter.states_.back()[2])
5455
<< "optimal value should be (1, 1)";
55-
EXPECT_FLOAT_EQ(0.99996597, parameter.states_.back()[2])
56+
EXPECT_FLOAT_EQ(0.99996597, parameter.states_.back()[3])
5657
<< "optimal value should be (1, 1)";
5758
EXPECT_FLOAT_EQ(return_code, 0);
5859
EXPECT_EQ(22, interrupt.call_count());

src/test/unit/services/optimize/newton_jacobian_test.cpp

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,11 @@ TEST_F(ServicesOptimize, withJacobian) {
3232

3333
EXPECT_FLOAT_EQ(return_code, 0);
3434

35-
ASSERT_EQ(2, parameter.names_.size());
35+
ASSERT_EQ(3, parameter.names_.size());
3636
EXPECT_EQ("lp__", parameter.names_[0]);
37-
EXPECT_EQ("sigma", parameter.names_[1]);
38-
EXPECT_NEAR((3 + std::sqrt(13)) / 2, parameter.states_.back()[1], 0.001);
37+
EXPECT_EQ("converged__", parameter.names_[1]);
38+
EXPECT_EQ("sigma", parameter.names_[2]);
39+
EXPECT_NEAR((3 + std::sqrt(13)) / 2, parameter.states_.back()[2], 0.001);
3940
EXPECT_GT(interrupt.call_count(), 0);
4041
}
4142

@@ -54,9 +55,10 @@ TEST_F(ServicesOptimize, withoutJacobian) {
5455

5556
EXPECT_FLOAT_EQ(return_code, 0);
5657

57-
ASSERT_EQ(2, parameter.names_.size());
58+
ASSERT_EQ(3, parameter.names_.size());
5859
EXPECT_EQ("lp__", parameter.names_[0]);
59-
EXPECT_EQ("sigma", parameter.names_[1]);
60-
EXPECT_NEAR(3, parameter.states_.back()[1], 0.001);
60+
EXPECT_EQ("converged__", parameter.names_[1]);
61+
EXPECT_EQ("sigma", parameter.names_[2]);
62+
EXPECT_NEAR(3, parameter.states_.back()[2], 0.001);
6163
EXPECT_GT(interrupt.call_count(), 0);
6264
}

src/test/unit/services/optimize/newton_test.cpp

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -36,20 +36,21 @@ TEST_F(ServicesOptimize, rosenbrock) {
3636
EXPECT_EQ(1, logger.find("Initial log joint probability = -1"));
3737
EXPECT_EQ(1, logger.find("Iteration 1. Log joint probability ="));
3838

39-
ASSERT_EQ(3, parameter.names_.size());
39+
ASSERT_EQ(4, parameter.names_.size());
4040
EXPECT_EQ("lp__", parameter.names_[0]);
41-
EXPECT_EQ("x", parameter.names_[1]);
42-
EXPECT_EQ("y", parameter.names_[2]);
41+
EXPECT_EQ("converged__", parameter.names_[1]);
42+
EXPECT_EQ("x", parameter.names_[2]);
43+
EXPECT_EQ("y", parameter.names_[3]);
4344

4445
EXPECT_GT(parameter.states_.size(), 0);
45-
EXPECT_FLOAT_EQ(0, parameter.states_.front()[1])
46-
<< "initial value should be (0, 0)";
4746
EXPECT_FLOAT_EQ(0, parameter.states_.front()[2])
4847
<< "initial value should be (0, 0)";
49-
EXPECT_NEAR(1, parameter.states_.back()[1], 1e-3)
50-
<< "optimal value should be (1, 1)";
48+
EXPECT_FLOAT_EQ(0, parameter.states_.front()[3])
49+
<< "initial value should be (0, 0)";
5150
EXPECT_NEAR(1, parameter.states_.back()[2], 1e-3)
5251
<< "optimal value should be (1, 1)";
52+
EXPECT_NEAR(1, parameter.states_.back()[3], 1e-3)
53+
<< "optimal value should be (1, 1)";
5354
EXPECT_FLOAT_EQ(return_code, 0);
5455
EXPECT_LT(0, interrupt.call_count());
5556
}
@@ -75,16 +76,17 @@ TEST_F(ServicesOptimize, rosenbrock_no_save_iterations) {
7576

7677
EXPECT_EQ("0,0\n", init_ss.str());
7778

78-
ASSERT_EQ(3, parameter.names_.size());
79+
ASSERT_EQ(4, parameter.names_.size());
7980
EXPECT_EQ("lp__", parameter.names_[0]);
80-
EXPECT_EQ("x", parameter.names_[1]);
81-
EXPECT_EQ("y", parameter.names_[2]);
81+
EXPECT_EQ("converged__", parameter.names_[1]);
82+
EXPECT_EQ("x", parameter.names_[2]);
83+
EXPECT_EQ("y", parameter.names_[3]);
8284

8385
EXPECT_EQ(1, parameter.states_.size());
84-
EXPECT_NEAR(1, parameter.states_.back()[1], 1e-3)
85-
<< "optimal value should be (1, 1)";
8686
EXPECT_NEAR(1, parameter.states_.back()[2], 1e-3)
8787
<< "optimal value should be (1, 1)";
88+
EXPECT_NEAR(1, parameter.states_.back()[3], 1e-3)
89+
<< "optimal value should be (1, 1)";
8890
EXPECT_FLOAT_EQ(return_code, 0);
8991
EXPECT_LT(0, interrupt.call_count());
9092
}

0 commit comments

Comments
 (0)