EigenOpt 1.0.0
Loading...
Searching...
No Matches
simplex_internal.hxx
Go to the documentation of this file.
1#pragma once
2
5
6
7namespace EigenOpt {
8
9namespace simplex {
10
11namespace internal {
12
13template<class Scalar>
15 const Eigen::Matrix<Scalar,Eigen::Dynamic,Eigen::Dynamic>& C,
16 const Eigen::Matrix<Scalar,Eigen::Dynamic,1>& d,
17 const Scalar& small_number,
18 std::vector<VariableDomain>& domains,
19 std::string& halt_reason
20)
21{
22 // Some syntactic sugar: function that tells if a number is "almost zero".
23 auto zero = [&](const Scalar& v) { return -small_number < v && v < small_number; };
24
25 // Deduce the number of constraints and variables.
26 const unsigned int m = C.rows();
27 const unsigned int n = C.cols();
28 domains = std::vector<VariableDomain>(n);
29
30 // For each row of C, check if a single element is non-zero.
31 for(int row=0; row<m; row++) {
32 int nzcol = -1;
33 for(int col=0; col<n; col++) {
34 if(!zero(C(row,col))) {
35 if(nzcol == -1) {
36 // New candidate.
37 nzcol = col;
38 }
39 else {
40 // Indicates that the row contains multiple non-zero elements.
41 nzcol = -2;
42 break;
43 }
44 }
45 }
46
47 // If no non-zero entry was found in a row of C, it means that a constraint
48 // is in the form 0*x<=d. This is a degenerate constraint and it will cause
49 // an error.
50 if(nzcol==-1) {
51 halt_reason = "The constraint matrix has row " + std::to_string(row) + " filled with zeros: the problem is degenerate.";
52 return false;
53 }
54
55 if(nzcol >= 0) {
56 // Found a candidate: check if the constraints implies non-negativity/non-positivity.
57 if(C(row,nzcol) < 0 && d(row) <= 0) {
58 // Add non-negativity constraint.
59 EigenOpt_SIMPLEX_DBG("variable " << nzcol << " has a non-negative constraint (row " << row << ")");
60 domains[nzcol].non_negative = true;
61 domains[nzcol].idx = row;
62 }
63 if(C(row,nzcol) > 0 && d(row) <= 0) {
64 // Make sure there wasn't a non-negativity constraint already as well.
65 if(domains[nzcol].non_negative) {
66 halt_reason = "Variable " + std::to_string(nzcol) + " has both non-negativity constraint (row " + std::to_string(domains[nzcol].idx) + ") and non-positivity constraint (row " + std::to_string(row) + ").";
67 return false;
68 }
69
70 // Add non-positivity constraint.
71 EigenOpt_SIMPLEX_DBG("variable " << nzcol << " has a non-positive constraint (row " << row << ")");
72 domains[nzcol].non_positive = true;
73 domains[nzcol].idx = row;
74 }
75 }
76 }
77
78 return true;
79}
80
81
82template<class Scalar>
83Eigen::Matrix<Scalar,Eigen::Dynamic,Eigen::Dynamic> transformation_matrix_from_domains(
84 const std::vector<VariableDomain>& domains
85 )
86{
87 // Typedefs 'VectorXs' and 'MatrixXs' - to make my life easier.
88 EigenOptTypedefs(Scalar);
89
90 // Number of variables in the original problem.
91 const unsigned int n = domains.size();
92
93 // Count how many working variables we will have:
94 // - If a variable can be positive, we add one working variable;
95 // - If a variable can be negative, we add one working variable;
96 // - If a variable can be both positive and negative, we add two working
97 // variables.
98 int nv = 0;
99 for(int i=0; i<n; i++) {
100 if(!domains[i].non_negative)
101 nv++;
102 if(!domains[i].non_positive)
103 nv++;
104 }
105
106 // Create the transformation matrix T.
107 MatrixXs T = MatrixXs::Zero(n,nv);
108
109 // Current column of T.
110 unsigned int col = 0;
111
112 // For each variable, add a 1 and/or a -1 where needed.
113 for(int i=0; i<n; i++) {
114 if(!domains[i].non_positive) {
115 T(i,col) = 1;
116 col++;
117 }
118 if(!domains[i].non_negative) {
119 T(i,col) = -1;
120 col++;
121 }
122 }
123 eigen_assert(col==nv && "INTERNAL ERROR WHILE INITIALIZING THE TRANSFORMATION MATRIX T: THE FINAL COLUMN COUNT DOES NOT EQUAL THE NUMBER OF WORKING VARIABLES");
124
125 return T;
126}
127
128
129template<class Scalar>
131 const Eigen::Matrix<Scalar,Eigen::Dynamic,Eigen::Dynamic>& C,
132 const Eigen::Matrix<Scalar,Eigen::Dynamic,1>& d,
133 const Scalar& small_number,
134 Eigen::Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic>& T,
135 std::string& halt_reason
136 )
137{
138 // Typedefs 'VectorXs' and 'MatrixXs' - to make my life easier.
139 EigenOptTypedefs(Scalar);
140
141 // Deduce the domain of each variable from the constraints, and use them to
142 // compute the transformation matrix.
143 std::vector<VariableDomain> domains;
144 if(!deduce_variables_domains(C, d, small_number, domains, halt_reason)) {
145 return false;
146 }
148 return true;
149}
150
151
152template<class Scalar>
153void pivot(
154 Eigen::Matrix<Scalar,Eigen::Dynamic,Eigen::Dynamic>& tableau,
157)
158{
159 // Perform gaussian elimination:
160 // 1. Normalize the leaving row by the coefficient of the entering variable.
162 // 2. For each other row, make sure the coefficient of the of the entering
163 // variable becomes zero. Note that we do not touch the bottom row - just
164 // for sake of versatility (there are cases in which we only want to
165 // perform the pivot on the upper part of the Tableau).
166 for(int row=0; row<tableau.rows()-1; row++) {
167 if(row!=leaving_variable) {
169 }
170 }
171}
172
173
174template<class Scalar>
176 Eigen::Matrix<Scalar,Eigen::Dynamic,Eigen::Dynamic>& tableau,
177 std::vector<int>& basic_variables,
178 const Scalar& small_number,
179 std::string& halt_reason
180)
181{
182 // Number of constraints and of variables.
183 const int m = tableau.rows() - 1;
184 const int n = tableau.cols() - 1;
185
186 int entering_col; // index of the entering variable
187
188 // Keep iterating until all objective coefficients become non-negative.
189 while(tableau.row(m).head(n).minCoeff(&entering_col) < -small_number) {
190 EigenOpt_SIMPLEX_DBG("Entering variable index: " << entering_col);
191
192 // Find the leaving variable: it will be the one for which the ratio:
193 // tableau(r, c) / tableau(r, -1)
194 // is the smallest non-negative value (in the expression above, c is the
195 // entering column and -1 means "last column").
196 int leaving_row = -1;
198 for(int row = 0; row<m; row++) {
201 if(leaving_row == -1 || ratio < minratio) {
203 minratio = ratio;
204 }
205 }
206 }
207
208 EigenOpt_SIMPLEX_DBG("Leaving row: " << (leaving_row!=-1?std::to_string(leaving_row):"none"));
209
210 if(leaving_row == -1) {
211 halt_reason = "No positive coefficient found in the tableau for the entering variable: the problem is unbounded.";
212 return false;
213 }
214
215 // Keep track of the new basic variable for the leaving row.
217
218 // Perform one step of Gaussian elimination.
220
221 EigenOpt_SIMPLEX_DBG("Tableau after Gaussian elimination:" << std::endl << tableau);
222
223 // Nullify the objective weight in the tableau.
225
226 EigenOpt_SIMPLEX_DBG("Tableau after objective nullification:" << std::endl << tableau);
227 }
228
229 return true;
230}
231
232
233template<class Scalar>
235 const Eigen::Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic>& C,
236 const Eigen::Matrix<Scalar, Eigen::Dynamic, 1>& d,
237 Eigen::Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic>& tableau,
238 std::vector<int>& basic_variables
239)
240{
241 // Typedefs 'VectorXs' and 'MatrixXs' - to make my life easier.
243
244 // Store number of variables and contraints.
245 const unsigned int m = C.rows();
246 const unsigned int n = C.cols();
247
248 // Count how many entries in d are negative; for each one of these,
249 // we will need an artifical variable.
250 const unsigned int na = std::count_if(d.data(), d.data()+m, [](const Scalar& x){return x<0;});
251 EigenOpt_SIMPLEX_DBG("Will use " << na << " artifical variables");
252
253 // Prepare the simplex tableau. The variable dcol is both the total number of
254 // variables (n working variables, m slack variables and na artificial
255 // variables) and the index of the last column of the tableau.
256 const unsigned int dcol = n + m + na;
257 tableau = MatrixXs::Zero(m+1, dcol+1);
258 basic_variables.resize(m);
259
260 // Fill the tableau row-by-row.
261 int ia = 0;
262 for(int i=0; i<m; i++) {
263 auto row = tableau.row(i);
264 if(d(i) < 0) {
265 // An artificial variable is needed for this constraint.
266 EigenOpt_SIMPLEX_DBG("Adding artificial-row to tableau");
267 basic_variables[i] = n + m + ia;
268 row.head(n) = -C.row(i);
269 row(n + i) = -1;
270 row(n + m + ia) = 1;
271 row(dcol) = -d(i);
272 ia++;
273 }
274 else {
275 // Just use the slack variable as usual.
276 EigenOpt_SIMPLEX_DBG("Adding slack-row to tableau");
277 basic_variables[i] = n + i;
278 row.head(n) = C.row(i);
279 row(dcol) = d(i);
280 row(n + i) = 1;
281 }
282 }
283}
284
285
286template<class Scalar>
288 Eigen::Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic>& tableau,
289 const std::vector<int>& basic_variables
290)
291{
292 // Perform Guassian elimination on the last row, so that at the end all basic
293 // coefficients are set to zero.
294 auto last_row = tableau.row(tableau.rows()-1);
295 for(unsigned int i=0; i<basic_variables.size(); i++) {
297 }
298}
299
300
301template<class Scalar>
303 const Eigen::Matrix<Scalar, Eigen::Dynamic, 1>& objective,
304 Eigen::Matrix<Scalar,Eigen::Dynamic,Eigen::Dynamic>& tableau,
305 std::vector<int>& basic_variables,
306 unsigned int na,
307 const Scalar& small_number,
308 std::string& halt_reason
309)
310{
311 // Calculate the number of slack variables and working variables.
312 const unsigned int m = tableau.rows() - 1;
313 const unsigned int nv = tableau.cols() - m - na - 1;
314
315 // If there is at least one artificial variable, we need to perform both
316 // steps. Otherwise, we can jump directly to the second step.
317 if(na > 0) {
318 EigenOpt_SIMPLEX_DBG("Adding weights for " << na << " artificial variables");
319
320 // Since we have artificial variables, we need to perform the first step.
321 // We start by adding a unit weight to each artificial variable in the
322 // objective.
323 for(unsigned int i=0; i<m; i++) {
324 if(basic_variables[i] >= nv + m) {
325 EigenOpt_SIMPLEX_DBG("Setting weight for tableau(" << m << "," << basic_variables[i] << ")");
326 tableau(m, basic_variables[i]) = 1;
327 }
328 }
329
330 EigenOpt_SIMPLEX_DBG("Tableau after adding artifical weights:" << std::endl << tableau);
331
332 // Use Gaussian elimination to update the last row of the tableau, so that
333 // the weight of basic variables are all set to zero.
335
336 EigenOpt_SIMPLEX_DBG("Tableau after objective elimination:" << std::endl << tableau);
337
338 // Now, run the simplex algorithm as usual.
340 return false;
341 }
342
343 EigenOpt_SIMPLEX_DBG("Simplex pivoting completed (Step 1).");
344
345 // After the solution, no artificial variable should be greater than zero.
346 for(unsigned int i=0; i<m; i++) {
347 if(basic_variables[i] >= nv + m && tableau(i, tableau.cols()-1) > small_number) {
348 halt_reason = "After pivoting, one artificial variable is still non-basic (p" + std::to_string(basic_variables[i] - nv - m) + " = " + std::to_string(tableau(i, tableau.cols()-1)) + ")";
349 return false;
350 }
351 }
352
353 // Swap basic artificial variables with non-basic ones.
354 for(unsigned int i=0; i<m; i++) {
355 // Skip non-artificial variables.
356 if(basic_variables[i] < nv + m)
357 continue;
358
359 EigenOpt_SIMPLEX_DBG("Looking for candidate to swap with p" + std::to_string(basic_variables[i] - nv - m));
360
361 // Find the first non-basic, non-artificial variable in the current row with non-zero coefficient.
362 int candidate = -1;
363 for(unsigned int j=0; j<nv+m; j++) {
364 if(std::find(basic_variables.begin(), basic_variables.end(), j)==basic_variables.end()) {
365 if(tableau(i, j) > small_number || tableau(i, j) < -small_number) {
366 candidate = j;
367 break;
368 }
369 }
370 }
371
372 // If no such variable is found, something is wrong...
373 if(candidate < 0) {
374 halt_reason = "After the first step, it was not possible to replace the artificial variable p" + std::to_string(basic_variables[i] - nv - m) + " with another non-basic, non-artificial variable.";
375 return false;
376 }
377
378 EigenOpt_SIMPLEX_DBG("Candidate: " + std::to_string(candidate));
379
380 // Swap the artificial variable and the non-basic one.
383 EigenOpt_SIMPLEX_DBG("Swapped " << basic_variables[i] << " (artificial, previously basic) and " << candidate << " (non-artificial, previously non-basic)");
384 EigenOpt_SIMPLEX_DBG("New Tableau:" << std::endl << tableau);
385 eigen_assert(tableau(i, tableau.cols()-1) > -small_number && "CRITICAL ISSUE DETECTED: AFTER SWAPPING ZERO-VALUED BASIC ARTIFICIAL VARIABLE, THE NEW BASIC VARIABLE IS NEGATIVE");
386 }
387
388 // We can remove the artificial variables from the tableau.
389 tableau.col(nv + m) = tableau.col(tableau.cols()-1);
390 tableau.conservativeResize(m+1, nv+m+1);
391
392 // Set objective weights in the bottom row.
393 tableau.bottomLeftCorner(1, nv) = objective.transpose();
394 tableau.bottomRightCorner(1, m+1).setZero();
395
396 EigenOpt_SIMPLEX_DBG("Tableau after removing artificial variables:" << std::endl << tableau);
397
398 // Use Gaussian elimination to update the last row of the tableau, so that
399 // the weight of basic variables are all set to zero (since at the end of
400 // the first step, at least one working variable will be basic).
402 }
403 else {
404 // Since we do not have any artificial variable, the initial tableau is in
405 // a feasible state. Just add the objective coefficients at the bottom of
406 // the tableau - no elimination is needed since working variables will all
407 // be in the non-basic set!
408 tableau.bottomLeftCorner(1, nv) = objective.transpose();
409 }
410
411 EigenOpt_SIMPLEX_DBG("Tableau after objective elimination:" << std::endl << tableau);
412
413 // Finish by running the simplex algorithm as usual.
415 return false;
416 }
417
418 EigenOpt_SIMPLEX_DBG("Simplex pivoting completed (Step 2).");
419 return true;
420}
421
422
423
424
425
426
427template<class Scalar>
429 const Eigen::Matrix<Scalar, Eigen::Dynamic, 1>& objective,
430 Eigen::Matrix<Scalar,Eigen::Dynamic,Eigen::Dynamic>& tableau,
431 std::vector<int>& basic_variables,
432 unsigned int na,
433 const Scalar& small_number,
434 const Scalar& large_number,
435 std::string& halt_reason
436)
437{
438 // Calculate the number of slack variables and working variables.
439 const unsigned int m = tableau.rows() - 1;
440 const unsigned int nv = tableau.cols() - m - na - 1;
441
442 // Copy the objective coefficients for the working variables.
443 EigenOpt_SIMPLEX_DBG("Adding objective coefficients for working variables");
444 tableau.bottomLeftCorner(1, nv) = objective.transpose();
445
446 // Add penalties for artificial variables.
447 EigenOpt_SIMPLEX_DBG("Adding penalties for " << na << " artificial variables");
448 for(unsigned int i=0; i<m; i++) {
449 if(basic_variables[i] >= nv + m) {
450 EigenOpt_SIMPLEX_DBG("Setting weight for tableau(" << m << "," << basic_variables[i] << ")");
452 }
453 }
454
455 EigenOpt_SIMPLEX_DBG("Tableau after adding weights:" << std::endl << tableau);
456
457 // Use Gaussian elimination to update the last row of the tableau, so that
458 // the weight of basic variables are all set to zero.
460
461 EigenOpt_SIMPLEX_DBG("Tableau after objective elimination:" << std::endl << tableau);
462
463 // Now, run the simplex algorithm as usual.
465 return false;
466 }
467
468 EigenOpt_SIMPLEX_DBG("Simplex pivoting completed.");
469
470 // After the solution, no artificial variable should be greater than zero.
471 for(unsigned int i=0; i<m; i++) {
472 if(basic_variables[i] >= nv + m && tableau(i, tableau.cols()-1) > small_number) {
473 halt_reason = "After pivoting, one artificial variable is still non-basic (p" + std::to_string(basic_variables[i] - nv - m) + " = " + std::to_string(tableau(i, tableau.cols()-1)) + ")";
474 return false;
475 }
476 }
477
478 return true;
479}
480
481} // internal
482
483} // simplex
484
485} // EigenOpt
Eigen::Matrix< Scalar, Eigen::Dynamic, Eigen::Dynamic > transformation_matrix_from_domains(const std::vector< VariableDomain > &domains)
Calculate a transformation matrix so that , .
void pivot(Eigen::Matrix< Scalar, Eigen::Dynamic, Eigen::Dynamic > &tableau, int entering_variable, int leaving_variable)
Perform a pivot operation between a basic and a non-basic variable.
bool deduce_variables_domains(const Eigen::Matrix< Scalar, Eigen::Dynamic, Eigen::Dynamic > &C, const Eigen::Matrix< Scalar, Eigen::Dynamic, 1 > &d, const Scalar &small_number, std::vector< VariableDomain > &domains, std::string &halt_reason)
Given a set of inequality constraints, deduce the domain of the decision variables.
bool simplex(Eigen::Matrix< Scalar, Eigen::Dynamic, Eigen::Dynamic > &tableau, std::vector< int > &basic_variables, const Scalar &small_number, std::string &halt_reason)
Perform successive pivot operations until a termination condition is met.
bool transformation_matrix(const Eigen::Matrix< Scalar, Eigen::Dynamic, Eigen::Dynamic > &C, const Eigen::Matrix< Scalar, Eigen::Dynamic, 1 > &d, const Scalar &small_number, Eigen::Matrix< Scalar, Eigen::Dynamic, Eigen::Dynamic > &T, std::string &halt_reason)
Calculate a transformation matrix so that , .
void create_tableau(const Eigen::Matrix< Scalar, Eigen::Dynamic, Eigen::Dynamic > &C, const Eigen::Matrix< Scalar, Eigen::Dynamic, 1 > &d, Eigen::Matrix< Scalar, Eigen::Dynamic, Eigen::Dynamic > &tableau, std::vector< int > &basic_variables)
Create a Simplex Tableau given a set of inequality constraints.
bool two_steps_method(const Eigen::Matrix< Scalar, Eigen::Dynamic, 1 > &objective, Eigen::Matrix< Scalar, Eigen::Dynamic, Eigen::Dynamic > &tableau, std::vector< int > &basic_variables, unsigned int na, const Scalar &small_number, std::string &halt_reason)
Solve a minimization problem using the two-steps Simplex method.
bool penalty_method(const Eigen::Matrix< Scalar, Eigen::Dynamic, 1 > &objective, Eigen::Matrix< Scalar, Eigen::Dynamic, Eigen::Dynamic > &tableau, std::vector< int > &basic_variables, unsigned int na, const Scalar &small_number, const Scalar &large_number, std::string &halt_reason)
Solve a minimization problem using the penalty Simplex method.
void eliminate_objective(Eigen::Matrix< Scalar, Eigen::Dynamic, Eigen::Dynamic > &tableau, const std::vector< int > &basic_variables)
Use Gaussian elimination on the last row of the tableau.
Main namespace of this project, containing all utilities.
#define EigenOpt_SIMPLEX_DBG(x)
#define EigenOptTypedefs(ScalarType)
Definition typedefs.hpp:6