-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathlp_simplex.hpp
More file actions
335 lines (274 loc) · 9.81 KB
/
lp_simplex.hpp
File metadata and controls
335 lines (274 loc) · 9.81 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
/**
* Written By Matthew Francis-Landau (2019)
*
* Implement a simplex linear programming solver
*/
#ifndef _CERTIFIEDCOSINE_LP_SIMPLEX
#define _CERTIFIEDCOSINE_LP_SIMPLEX
#include <Eigen/Dense>
#include <vector>
#ifdef DEBUG_SIMPLEX_PRINT
#include <iostream>
#endif
namespace certified_cosine {
using namespace std;
/**
* solving the LP using the simplex method. This is unable to handle the ||x|| <= 1 constraint
* as we are moving along the edge of the walls instead of having the value be zero.
* This needs to identify the cases where it has already left the unit ball and then project back into the ball
* or then have this check if it is still inside of the cone region
*
* In the case that a new constraint is added, that is currently violated, then this is going to have to identify
* some way to undo the constraint
* - this seems to be using a "dual simplex" method where it is optimizing over which constraints are used?4
*/
template <typename float_t>
struct LPSolverSimplex {
// the tableau as standard with simplex, pivots performed on the table
// directly which modifies any relevant rows/columns.
//
// We want to be able to solve x \in (-\inf, \inf), rather than x \in [0,
// \inf) as is more typical with simplex. This means that we will switch
// the signs on the columns as required
// column 0 is the b values for a constraint
Eigen::Matrix<float_t, -1, -1, Eigen::RowMajor> tableau;
// the objective that we are trying to maximize
Eigen::Matrix<float_t, -1, 1> c;
std::vector<bool> is_negative; // if the current variable is negated in its value
// optimizations to track which rows and columns are being used as the basis
std::vector<uint> row_basis;
std::vector<uint> col_basis; // then this can track which values was the basis and unset it
uint nvars;
uint nconst;
void resize(int nconst, int nvars) {
if (tableau.rows() < nconst + 1 || tableau.cols() < nconst + nvars + 1) {
decltype(tableau) nC = decltype(tableau)::Zero(nconst + 1, nvars + nconst + 1);
nC.block(0, 0, tableau.rows(), tableau.cols()) = tableau;
tableau.swap(nC);
is_negative.resize(nvars);
row_basis.resize(tableau.cols());
col_basis.resize(tableau.rows());
}
}
auto &get_tableau() { return tableau.block(0, 0, nconst + 1, nvars + nconst + 1); }
void resize(int min_size) {
if (tableau.rows() < min_size + 1) {
resize(max(min_size, (int)(tableau.rows() * 2)), nvars);
}
}
static constexpr float_t epsilon = .002;
// reset the solver given the new table
// Solve Ax <= b , c^Tx <= 1 for max c^T x
// d = A^T c the constraints distances as precomputed
void load_tableau(const Eigen::Matrix<float_t, -1, -1> &A, // these should be auto so it can be a block
const Eigen::Matrix<float_t, -1, 1> &b, const Eigen::Matrix<float_t, -1, 1> &c
/*const Eigen::Matrix<float_t, -1, 1> &d*/) {
this->c = c;
nvars = c.rows(); //+ 1;
nconst = b.rows() + 1;
assert(A.rows() == nconst - 1);
assert(A.cols() == nvars);
// this is going have the slack variables that are added to each of constraints
if (tableau.rows() < nconst + 1 || tableau.cols() < nvars + nconst + 1) {
tableau.resize(nconst + 1, nvars + nconst + 1);
row_basis.resize(tableau.cols());
col_basis.resize(tableau.rows());
}
is_negative.clear();
is_negative.resize(nvars);
tableau.setZero(); // faster if we don't clear maybe
// b is in the far left column, (rather than the far right like usually drawn)
// column 1 is the d values
assert((b.array() > 0).all());
tableau.block(2, 0, nconst - 1, 1) = b;
tableau.block(0, 1, 1, nvars) = -c.transpose();
tableau.block(1, 1, 1, nvars) = c.transpose(); // set c^T x <= 1
tableau(1, 0) = 1;
tableau.block(2, 1, nconst - 1, nvars) = A;
tableau.block(1, 1 + nvars, nconst, nconst).setIdentity();
for (uint i = 0; i <= nvars; i++) {
row_basis[i] = 0;
}
for (uint i = 0; i < nconst; i++) {
col_basis[i + 1] = 1 + nvars + i;
row_basis[1 + nvars + i] = i + 1;
}
}
void switch_sign(uint idx) {
tableau.col(idx + 1) *= -1;
is_negative[idx].flip(); // ^= true; // ..... the joys of using vector<bool>
}
void add_constraint(const Eigen::Matrix<float_t, -1, 1> &a, float_t b, float_t d = 0.0) {
// add the constraint Ax <= b
assert(b > 0);
assert(a.size() == nvars);
nconst++;
resize(nconst, nvars);
tableau.col(nvars + nconst).setZero();
tableau.block(nconst, 1 + nvars, 1, nconst).setZero();
tableau(nconst, nvars + nconst) = 1;
tableau(nconst, 0) = b;
for (uint i = 0; i < nvars; i++) {
if (is_negative[i]) {
tableau(nconst, i + 1) = -a(i);
} else {
tableau(nconst, i + 1) = a(i);
}
}
col_basis[nconst] = nvars + nconst;
row_basis[nvars + nconst] = nconst;
// transform the new constraint into the currently active basis
auto self = tableau.block(nconst, 0, 1, nvars + nconst);
for (uint i = 0; i < nvars; i++) {
// check if the current column is a basis, then we are going to subtract it from our row
const uint rb = row_basis[i + 1];
float_t v = self(0, i + 1);
if (rb && col_basis[rb] == i + 1 && v != 0) {
self -= tableau.block(rb, 0, 1, self.cols()) * v;
}
}
}
inline float_t objective() { return tableau(0, 0); }
bool is_constraint_tight(uint c = 0) { return col_basis[row_basis[c]] == c; }
Eigen::Matrix<float_t, 1, -1> get_x() {
Eigen::Matrix<float_t, 1, -1> ret(nvars);
get_x(ret);
return ret;
}
void get_x(Eigen::Matrix<float_t, 1, -1> &ret) {
for (uint i = 0; i < nvars; i++) {
const uint rb = row_basis[i + 1];
if (rb && col_basis[rb] == i + 1) {
assert(abs(tableau(0, i + 1)) < .0005);
assert(tableau(rb, i + 1) == 1);
ret(i) = tableau(rb, 0) * (is_negative[i] ? -1 : 1);
} else {
ret(i) = 0;
}
}
}
Eigen::Matrix<float_t, 1, -1> get_x_vec() {
Eigen::Matrix<float_t, 1, -1> v1(nvars);
get_x(v1);
return v1;
}
bool check_solution() {
// check that the solution to the simplex respects all of the constraints
return (tableau.block(1, 0, nconst, 1).array() >= -.005).all();
}
void do_pivot(const int pivotRow, const int pivotColumn) {
#ifdef DEBUG_SIMPLEX_PRINT
cout << "pivot: " << pivotRow << " " << pivotColumn << " " << objective() << endl;
#endif
// divide out the row
float_t div = tableau(pivotRow, pivotColumn);
const int ncols = nvars + nconst + 1;
auto prow = tableau.block(pivotRow, 0, 1, ncols);
assert(div != 0);
prow /= div;
// track where the basis is
row_basis[col_basis[pivotRow]] = 0;
col_basis[pivotRow] = pivotColumn;
row_basis[pivotColumn] = pivotRow;
for (uint i = 0; i <= nconst; i++) { // there might be extra rows that are stored
if (i != pivotRow) {
float_t val = tableau(i, pivotColumn);
if (val != 0) {
tableau.block(i, 0, 1, ncols) -= prow * val;
}
}
}
assert(tableau(0, pivotColumn) == 0);
assert(tableau(pivotRow, pivotColumn) == 1);
}
bool run_dual_simplex() {
// this descreases the value to ensure that all of the constriants are satasified
int pivotColumn, pivotRow;
while (true) {
float_t val = numeric_limits<float_t>::infinity();
pivotRow = -1;
for (uint i = 0; i < nconst; i++) {
float_t v = tableau(i + 1, 0);
if (v < val) {
val = v;
pivotRow = i;
}
}
// then something has gone wrong. There seems to be some floating point error that accumulates, and causes
// problems
if (pivotRow == -1) return false;
pivotRow++;
if (val >= 0) break; // done if we have nothing worse that epsilon
val = numeric_limits<float_t>::infinity();
pivotColumn = -1;
for (uint i = 0; i < nvars + nconst; i++) {
float_t n = tableau(0, i + 1);
float_t d = tableau(pivotRow, i + 1);
if (d >= 0) continue;
float_t v = -n / d;
if (v < val) {
val = v;
pivotColumn = i;
}
}
if (pivotColumn == -1) return false;
pivotColumn++;
do_pivot(pivotRow, pivotColumn);
}
#ifdef CERTIFIEDCOSINE_DEBUG
assert(check_solution());
#endif
return true;
}
void run_simplex() {
int pivotColumn, pivotRow;
while (objective() < 1 - epsilon) {
assert((tableau.block(1, 0, nconst, 1).array() >= -.005).all());
float_t val = numeric_limits<float_t>::infinity();
pivotColumn = -1;
for (uint i = 0; i < nvars; i++) {
float_t v = tableau(0, i + 1);
if (v > 0) v *= -1;
if (v < val) {
val = v;
pivotColumn = i;
}
}
for (uint i = 0; i < nconst; i++) {
float_t v = tableau(0, nvars + 1 + i);
if (v < val) {
val = v;
pivotColumn = i + nvars;
};
}
assert(pivotColumn != -1);
if (val > -epsilon) return; // the amount we can improve is not that much
// switch the sign of the cases where this is a "negative" value
if (pivotColumn < nvars && tableau(0, pivotColumn + 1) > 0) {
switch_sign(pivotColumn);
}
pivotColumn++;
float_t val1 = val;
// find the pivotRow
val = numeric_limits<float_t>::infinity();
pivotRow = -1;
for (uint i = 1; i <= nconst; i++) {
float_t d = tableau(i, pivotColumn);
float_t v = tableau(i, 0) / d;
if (d <= 0) continue;
assert(v > 0);
if (v < val) {
val = v;
pivotRow = i;
}
}
assert(pivotRow != -1);
do_pivot(pivotRow, pivotColumn);
#ifdef CERTIFIEDCOSINE_DEBUG
assert(check_solution());
#endif
}
}
};
} // namespace certified_cosine
#endif