Skip to content

Commit 56bee77

Browse files
author
Stefan Haan
committed
randomized solver
1 parent da34a70 commit 56bee77

File tree

4 files changed

+105
-38
lines changed

4 files changed

+105
-38
lines changed

R/RcppExports.R

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ compute_parameter_transformations <- function(reduced_coefficients, factor_loadi
2727
.Call(`_bayesianVARs_compute_parameter_transformations`, reduced_coefficients, factor_loadings, U_vecs, logvar_T, include_facload, include_B0_inv_t, include_B0, include_structural_coeff, include_long_run_ir)
2828
}
2929

30-
find_rotation_cpp <- function(parameter_transformations, restriction_specs, tol = 1e-6) {
31-
.Call(`_bayesianVARs_find_rotation_cpp`, parameter_transformations, restriction_specs, tol)
30+
find_rotation_cpp <- function(parameter_transformations, restriction_specs, solver_option = "randomized", randomized_max_attempts = 100L, tol = 1e-6) {
31+
.Call(`_bayesianVARs_find_rotation_cpp`, parameter_transformations, restriction_specs, solver_option, randomized_max_attempts, tol)
3232
}
3333

3434
irf_cpp <- function(coefficients, factor_loadings, U_vecs, logvar_t, shocks, ahead, rotation_ = NULL) {

R/irf.R

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,9 @@ find_rotation <- function(
106106
restrictions_B0 = NULL,
107107
restrictions_structural_coeff = NULL,
108108
restrictions_long_run_ir = NULL,
109-
lp_solve_verbosity = 3
109+
110+
solver = "randomized",
111+
randomized_max_attempts = 100
110112
) {
111113
if (x$sigma_type == "factor") {
112114
forbidden_restrictions <- c(
@@ -152,6 +154,8 @@ find_rotation <- function(
152154

153155
find_rotation_cpp(
154156
parameter_transformations = parameter_transformations,
155-
restriction_specs = restrictions[lengths(restrictions) > 0]
157+
restriction_specs = restrictions[lengths(restrictions) > 0],
158+
solver_option = solver,
159+
randomized_max_attempts = randomized_max_attempts
156160
)
157161
}

src/RcppExports.cpp

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,15 +76,17 @@ BEGIN_RCPP
7676
END_RCPP
7777
}
7878
// find_rotation_cpp
79-
arma::cube find_rotation_cpp(const arma::field<arma::cube>& parameter_transformations, const arma::field<Rcpp::NumericMatrix>& restriction_specs, const double tol);
80-
RcppExport SEXP _bayesianVARs_find_rotation_cpp(SEXP parameter_transformationsSEXP, SEXP restriction_specsSEXP, SEXP tolSEXP) {
79+
arma::cube find_rotation_cpp(const arma::field<arma::cube>& parameter_transformations, const arma::field<Rcpp::NumericMatrix>& restriction_specs, const std::string& solver_option, const arma::uword randomized_max_attempts, const double tol);
80+
RcppExport SEXP _bayesianVARs_find_rotation_cpp(SEXP parameter_transformationsSEXP, SEXP restriction_specsSEXP, SEXP solver_optionSEXP, SEXP randomized_max_attemptsSEXP, SEXP tolSEXP) {
8181
BEGIN_RCPP
8282
Rcpp::RObject rcpp_result_gen;
8383
Rcpp::RNGScope rcpp_rngScope_gen;
8484
Rcpp::traits::input_parameter< const arma::field<arma::cube>& >::type parameter_transformations(parameter_transformationsSEXP);
8585
Rcpp::traits::input_parameter< const arma::field<Rcpp::NumericMatrix>& >::type restriction_specs(restriction_specsSEXP);
86+
Rcpp::traits::input_parameter< const std::string& >::type solver_option(solver_optionSEXP);
87+
Rcpp::traits::input_parameter< const arma::uword >::type randomized_max_attempts(randomized_max_attemptsSEXP);
8688
Rcpp::traits::input_parameter< const double >::type tol(tolSEXP);
87-
rcpp_result_gen = Rcpp::wrap(find_rotation_cpp(parameter_transformations, restriction_specs, tol));
89+
rcpp_result_gen = Rcpp::wrap(find_rotation_cpp(parameter_transformations, restriction_specs, solver_option, randomized_max_attempts, tol));
8890
return rcpp_result_gen;
8991
END_RCPP
9092
}
@@ -230,7 +232,7 @@ static const R_CallMethodDef CallEntries[] = {
230232
{"_bayesianVARs_bvar_cpp", (DL_FUNC) &_bayesianVARs_bvar_cpp, 21},
231233
{"_bayesianVARs_my_gig", (DL_FUNC) &_bayesianVARs_my_gig, 4},
232234
{"_bayesianVARs_compute_parameter_transformations", (DL_FUNC) &_bayesianVARs_compute_parameter_transformations, 9},
233-
{"_bayesianVARs_find_rotation_cpp", (DL_FUNC) &_bayesianVARs_find_rotation_cpp, 3},
235+
{"_bayesianVARs_find_rotation_cpp", (DL_FUNC) &_bayesianVARs_find_rotation_cpp, 5},
234236
{"_bayesianVARs_irf_cpp", (DL_FUNC) &_bayesianVARs_irf_cpp, 7},
235237
{"_bayesianVARs_irf_from_true_parameters", (DL_FUNC) &_bayesianVARs_irf_from_true_parameters, 3},
236238
{"_bayesianVARs_sample_PHI_cholesky", (DL_FUNC) &_bayesianVARs_sample_PHI_cholesky, 7},

src/irf.cpp

Lines changed: 91 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
#include <memory>
12
#include <lp_lib.h>
23
#include "utilities_cpp.h"
34

@@ -135,8 +136,15 @@ mat construct_sign_restriction (const NumericMatrix::ConstColumn& spec) {
135136
return sign_restriction_matrix;
136137
}
137138

138-
139139
class Solver {
140+
public:
141+
virtual void add_constraints(mat& constraints, int constr_type, double rhs) = 0;
142+
virtual vec solve() = 0;
143+
virtual void recycle() = 0;
144+
virtual void print() = 0;
145+
};
146+
147+
class LPSolver : public Solver {
140148
using make_lp_func_ptr= lprec*(*)(int, int);
141149
template<typename R, typename... T> using lp_func_ptr = R(*)(lprec*, T...);
142150

@@ -172,7 +180,7 @@ class Solver {
172180
int n_initial_rows;
173181

174182
public:
175-
Solver(const uword dim_x) : n(dim_x) {
183+
LPSolver(const uword dim_x) : n(dim_x) {
176184
lp = (*make_lp)(0, 3*n);
177185
if (lp == NULL) throw std::bad_alloc();
178186

@@ -206,7 +214,7 @@ class Solver {
206214
(*set_binary)(lp, y_cols[j], TRUE); //y_i in {0, 1}
207215
}
208216

209-
//setup constraints
217+
//setup initial constraints
210218
//x_i must not be within the interval [-U_i, U_i]
211219
//y_i switches if x_i is right to the interval or left to the interval
212220
//=> in an optimal solution, U_i is the greatest possible value of |x_i|
@@ -222,45 +230,96 @@ class Solver {
222230
n_initial_rows = (*get_Nrows)(lp);
223231
}
224232

225-
void add_constraint(double* x_coeff_ptr, int constr_type, double rhs) {
226-
const bool success = (*add_constraintex)(lp, n, x_coeff_ptr, x_cols.memptr(), constr_type, rhs);
227-
if (success == FALSE) {
228-
throw std::runtime_error("Could not add constraint");
229-
}
233+
void add_constraints(mat& constraints, int constr_type, double rhs) override {
234+
constraints.each_row([&](rowvec& row) {
235+
const bool success = (*add_constraintex)(lp, n, row.memptr(), x_cols.memptr(), constr_type, rhs);
236+
if (success == FALSE) {
237+
throw std::runtime_error("Could not add constraints");
238+
}
239+
});
230240
}
231241

232-
vec solve() {
242+
vec solve() override {
233243
(*set_add_rowmode)(lp, FALSE);
234244
int ret = (*lp_solve)(lp);
235245
if(ret != 0) {
246+
print();
236247
throw std::logic_error((*get_statustext)(lp, ret));
237248
};
238249
vec lp_vars_store(3*n);
239250
(*get_variables)(lp, lp_vars_store.memptr());
240251
return lp_vars_store.head_rows(n); // only return x
241252
}
242253

243-
void recycle() {
254+
void recycle() override {
244255
// delete all non-initial constraints
245256
(*resize_lp)(lp, n_initial_rows, (*get_Ncolumns)(lp));
246257
(*set_add_rowmode)(lp, TRUE);
247258
}
248259

249-
void print() {
260+
void print() override {
250261
(*set_add_rowmode)(lp, FALSE);
251262
(*print_lp)(lp);
252263
}
253264

254-
~Solver() {
265+
~LPSolver() {
255266
(*delete_lp)(lp);
256267
}
257268

258269
};
259270

271+
class RandomizedSolver : public Solver {
272+
private:
273+
mat zero_restrictions;
274+
mat sign_restrictions;
275+
const double tol;
276+
const uword max_attempts;
277+
public:
278+
RandomizedSolver(const uword dim_x, const uword max_attempts, const double tol) :
279+
zero_restrictions(mat(0, dim_x)),
280+
sign_restrictions(mat(0, dim_x)),
281+
tol(tol), max_attempts(max_attempts)
282+
{}
283+
284+
void add_constraints(mat& constraints, int constr_type, double rhs) override {
285+
if ( constr_type == EQ && rhs == 0) {
286+
zero_restrictions = join_vert(zero_restrictions, constraints);
287+
}
288+
else if ( constr_type == GE && rhs == 0) {
289+
sign_restrictions = join_vert(sign_restrictions, constraints);
290+
}
291+
else throw std::domain_error("Constraint type not implemented");
292+
};
293+
virtual vec solve() override {
294+
const mat zero_restrictions_solution_space = null(zero_restrictions, tol);
295+
for (uword i = 0; i < max_attempts; i++) {
296+
// draw random unit-length vector
297+
vec v(zero_restrictions_solution_space.n_cols);
298+
v.imbue(R::norm_rand);
299+
v = normalise(v);
300+
301+
const vec x_candidate = zero_restrictions_solution_space*v;
302+
if (all(sign_restrictions * x_candidate >= 0-tol))
303+
return x_candidate;
304+
}
305+
throw std::runtime_error("Max attempts reached while searching for solution");
306+
};
307+
virtual void recycle() override {
308+
zero_restrictions = mat(0, zero_restrictions.n_cols);
309+
sign_restrictions = mat(0, sign_restrictions.n_cols);
310+
};
311+
virtual void print() override {
312+
Rcout << "Zero restrictions:" << endl << zero_restrictions <<
313+
"Sign restrictions:" << endl << sign_restrictions;
314+
};
315+
};
316+
260317
// [[Rcpp::export]]
261318
arma::cube find_rotation_cpp(
262319
const arma::field<arma::cube>& parameter_transformations, //each field element: rows: transformation size, cols: variables, slices: draws
263320
const arma::field<Rcpp::NumericMatrix>& restriction_specs, //each field element: rows: transformation size, cols: variables
321+
const std::string& solver_option = "randomized", // "randomized" or "lp"
322+
const arma::uword randomized_max_attempts = 100, // ignored when solver is "lp"
264323
const double tol = 1e-6
265324
) {
266325
//algorithm from RUBIO-RAMÍREZ ET AL. (doi: 10.1111/j.1467-937X.2009.00578.x)
@@ -272,6 +331,15 @@ arma::cube find_rotation_cpp(
272331
const uword n_posterior_draws = parameter_transformations(0).n_slices;
273332
cube rotation(n_variables, n_variables, n_posterior_draws, fill::none);
274333

334+
std::unique_ptr<Solver> solver;
335+
if (solver_option == "randomized") {
336+
solver.reset(new RandomizedSolver(n_variables, randomized_max_attempts, tol));
337+
}
338+
else if (solver_option == "lp") {
339+
solver.reset(new LPSolver(n_variables));
340+
}
341+
else throw std::domain_error("Unknown solver option");
342+
275343
//field rows: tranformations, field cols: cols of the transformation
276344
//each field element: rows: number of restrictions, cols: transformation size
277345
field<mat> zero_restrictions(restriction_specs.n_elem, n_variables);
@@ -292,43 +360,36 @@ arma::cube find_rotation_cpp(
292360
//since each column must be orthogonal to the ones that came before,
293361
//we start with the column with the most restrictions
294362
uvec col_order = sort_index(2 * n_zero_restrictions + n_sign_restrictions, "descend");
295-
Solver solver(n_variables);
296363
for (uword r = 0; r < n_posterior_draws; r++) {
297364
if (r % 512 == 0) Rcpp::checkUserInterrupt();
298-
for (uword j_index = 0; j_index < n_variables; solver.recycle(), j_index++) {
365+
for (uword j_index = 0; j_index < n_variables; solver->recycle(), j_index++) {
299366
const uword j = col_order[j_index];
300367

301368
// the column j of the rotation matrix must be orthogonal to columns that came before
302-
for (uword j_index_other = 0; j_index_other < j_index; j_index_other++) {
303-
const uword j_other = col_order[j_index_other];
304-
solver.add_constraint(rotation.slice(r).colptr(j_other), EQ, 0);
305-
}
369+
mat previous_columns = rotation.slice(r).cols(col_order.head(j_index)).t();
370+
solver->add_constraints(previous_columns, EQ, 0);
306371

307-
// add zero restrictions
308-
for (uword i = 0; i < parameter_transformations.n_elem; i++) {
309-
mat zero_constraints = zero_restrictions(i, j) * parameter_transformations(i).slice(r);
310-
zero_constraints.each_row([&](rowvec& row) {
311-
solver.add_constraint(row.memptr(), EQ, 0);
312-
});
372+
if (n_zero_restrictions(j) > 0) {
373+
for (uword i = 0; i < parameter_transformations.n_elem; i++) {
374+
mat zero_constraints = zero_restrictions(i, j) * parameter_transformations(i).slice(r);
375+
solver->add_constraints(zero_constraints, EQ, 0);
376+
}
313377
}
314378

315-
//add sign restrictions
316379
if (n_sign_restrictions(j) > 0) {
317380
for (uword i = 0; i < parameter_transformations.n_elem; i++) {
318381
mat sign_constraints = sign_restrictions(i, j) * parameter_transformations(i).slice(r);
319-
sign_constraints.each_row([&](rowvec& row){
320-
solver.add_constraint(row.memptr(), GE, 0);
321-
});
382+
solver->add_constraints(sign_constraints, GE, 0);
322383
}
323384
}
324385

325-
const vec p_j = solver.solve().clean(tol);
386+
const vec p_j = solver->solve().clean(tol);
326387
if (p_j.is_zero()) {
327388
//zero was the optimal solution
328389
Rcerr << "Cannot satisfy restrictions for posterior sample: #" << r
329390
<< ", column:" << j+1
330391
<< " (" << j_index+1 << "-th in order of rank)" << endl;
331-
solver.print();
392+
solver->print();
332393
throw std::logic_error("Could not satisfy restrictions");
333394
}
334395
rotation.slice(r).col(j) = normalise(p_j);

0 commit comments

Comments
 (0)