1
+ #include < memory>
1
2
#include < lp_lib.h>
2
3
#include " utilities_cpp.h"
3
4
@@ -135,8 +136,15 @@ mat construct_sign_restriction (const NumericMatrix::ConstColumn& spec) {
135
136
return sign_restriction_matrix;
136
137
}
137
138
138
-
139
139
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 {
140
148
using make_lp_func_ptr= lprec*(*)(int , int );
141
149
template <typename R, typename ... T> using lp_func_ptr = R(*)(lprec*, T...);
142
150
@@ -172,7 +180,7 @@ class Solver {
172
180
int n_initial_rows;
173
181
174
182
public:
175
- Solver (const uword dim_x) : n(dim_x) {
183
+ LPSolver (const uword dim_x) : n(dim_x) {
176
184
lp = (*make_lp)(0 , 3 *n);
177
185
if (lp == NULL ) throw std::bad_alloc ();
178
186
@@ -206,7 +214,7 @@ class Solver {
206
214
(*set_binary)(lp, y_cols[j], TRUE ); // y_i in {0, 1}
207
215
}
208
216
209
- // setup constraints
217
+ // setup initial constraints
210
218
// x_i must not be within the interval [-U_i, U_i]
211
219
// y_i switches if x_i is right to the interval or left to the interval
212
220
// => in an optimal solution, U_i is the greatest possible value of |x_i|
@@ -222,45 +230,96 @@ class Solver {
222
230
n_initial_rows = (*get_Nrows)(lp);
223
231
}
224
232
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
+ });
230
240
}
231
241
232
- vec solve () {
242
+ vec solve () override {
233
243
(*set_add_rowmode)(lp, FALSE );
234
244
int ret = (*lp_solve)(lp);
235
245
if (ret != 0 ) {
246
+ print ();
236
247
throw std::logic_error ((*get_statustext)(lp, ret));
237
248
};
238
249
vec lp_vars_store (3 *n);
239
250
(*get_variables)(lp, lp_vars_store.memptr ());
240
251
return lp_vars_store.head_rows (n); // only return x
241
252
}
242
253
243
- void recycle () {
254
+ void recycle () override {
244
255
// delete all non-initial constraints
245
256
(*resize_lp)(lp, n_initial_rows, (*get_Ncolumns)(lp));
246
257
(*set_add_rowmode)(lp, TRUE );
247
258
}
248
259
249
- void print () {
260
+ void print () override {
250
261
(*set_add_rowmode)(lp, FALSE );
251
262
(*print_lp)(lp);
252
263
}
253
264
254
- ~Solver () {
265
+ ~LPSolver () {
255
266
(*delete_lp)(lp);
256
267
}
257
268
258
269
};
259
270
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
+
260
317
// [[Rcpp::export]]
261
318
arma::cube find_rotation_cpp (
262
319
const arma::field<arma::cube>& parameter_transformations, // each field element: rows: transformation size, cols: variables, slices: draws
263
320
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"
264
323
const double tol = 1e-6
265
324
) {
266
325
// 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(
272
331
const uword n_posterior_draws = parameter_transformations (0 ).n_slices ;
273
332
cube rotation (n_variables, n_variables, n_posterior_draws, fill::none);
274
333
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
+
275
343
// field rows: tranformations, field cols: cols of the transformation
276
344
// each field element: rows: number of restrictions, cols: transformation size
277
345
field<mat> zero_restrictions (restriction_specs.n_elem , n_variables);
@@ -292,43 +360,36 @@ arma::cube find_rotation_cpp(
292
360
// since each column must be orthogonal to the ones that came before,
293
361
// we start with the column with the most restrictions
294
362
uvec col_order = sort_index (2 * n_zero_restrictions + n_sign_restrictions, " descend" );
295
- Solver solver (n_variables);
296
363
for (uword r = 0 ; r < n_posterior_draws; r++) {
297
364
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++) {
299
366
const uword j = col_order[j_index];
300
367
301
368
// 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 );
306
371
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
+ }
313
377
}
314
378
315
- // add sign restrictions
316
379
if (n_sign_restrictions (j) > 0 ) {
317
380
for (uword i = 0 ; i < parameter_transformations.n_elem ; i++) {
318
381
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 );
322
383
}
323
384
}
324
385
325
- const vec p_j = solver. solve ().clean (tol);
386
+ const vec p_j = solver-> solve ().clean (tol);
326
387
if (p_j.is_zero ()) {
327
388
// zero was the optimal solution
328
389
Rcerr << " Cannot satisfy restrictions for posterior sample: #" << r
329
390
<< " , column:" << j+1
330
391
<< " (" << j_index+1 << " -th in order of rank)" << endl;
331
- solver. print ();
392
+ solver-> print ();
332
393
throw std::logic_error (" Could not satisfy restrictions" );
333
394
}
334
395
rotation.slice (r).col (j) = normalise (p_j);
0 commit comments