Cogs.Core
Simplex.cpp
1#include "Simplex.h"
2
3#include <vector>
4#include <cstdio>
5#include <algorithm>
6
7#include "Context.h"
8
9namespace
10{
11 struct LinearProblem
12 {
13 int nonbasicVars;
14 int basicVars;
15 int stride;
16 int* basicVar;
17 int* nonbasicVar;
18 double* dictionary;
19 double* pivotColumnTmp;
20 double* objectiveTmp;
21 bool printSteps;
22
23 void initializeDictionary(const double* objective,
24 const double* restrictions);
25
26 void prettyPrint(const char* what);
27
28 void pivot(int j, int i);
29
30 bool isSolutionFeasible();
31
32 int basicVarIndex(int variable);
33
34 int nonbasicVarIndex(int variable);
35
36 void emitSolution(double* solution);
37
38 int findPivotColumn();
39
40 int findPivotRow(int pivotColumn);
41
42 int findPivotColumnBland();
43
44 int findPivotRowBland(int pivotColumn);
45
46 Cogs::Core::SimplexResult search(int maxIterations);
47
48 Cogs::Core::SimplexResult maximize(double* solution, const int maxIterations);
49 };
50
51 int LinearProblem::basicVarIndex(int variable)
52 {
53 for (int k = 0; k < basicVars; k++) {
54 if (basicVar[k] == variable) {
55 return k;
56 }
57 }
58 return -1;
59 }
60
61 int LinearProblem::nonbasicVarIndex(int variable)
62 {
63 for (int l = 0; l < nonbasicVars; l++) {
64 if (nonbasicVar[l] == variable) {
65 return l;
66 }
67 }
68 return -1;
69 }
70
71 void LinearProblem::initializeDictionary(const double* objective,
72 const double* restrictions)
73 {
74 for (int j = 0; j < basicVars; j++) {
75 dictionary[j*stride + 0] = restrictions[j*(nonbasicVars + 1) + nonbasicVars];
76 for (int i = 0; i < nonbasicVars; i++) {
77 dictionary[j*stride + i + 1] = -restrictions[j*(nonbasicVars + 1) + i];
78 }
79 }
80 dictionary[basicVars*stride + 0] = 0.0;
81 for (int i = 0; i < nonbasicVars; i++) {
82 dictionary[basicVars*stride + i + 1] = objective[i];
83 }
84
85 for (int i = 0; i < nonbasicVars; i++) {
86 nonbasicVar[i] = i + 1;
87 }
88 for (int i = 0; i < basicVars; i++) {
89 basicVar[i] = nonbasicVars + i + 1;
90 }
91 }
92
93 void LinearProblem::prettyPrint(const char* what)
94 {
95 printf("%s:\n", what);
96
97 std::vector<std::pair<int, int>> colPerm(nonbasicVars + 1);
98 colPerm[0] = std::make_pair(0, 0);
99 for (int i = 0; i < nonbasicVars; i++) {
100 colPerm[i + 1] = std::make_pair(nonbasicVar[i], i + 1);
101 }
102 std::sort(colPerm.begin() + 1, colPerm.end(), [](auto & a, auto & b) -> bool {return a.first < b.first; });
103
104 std::vector<std::pair<int, int>> rowPerm(basicVars);
105 for (int i = 0; i < basicVars; i++) {
106 rowPerm[i] = std::make_pair(basicVar[i], i);
107 }
108 std::sort(rowPerm.begin(), rowPerm.end(), [](auto & a, auto & b) -> bool {return a.first < b.first; });
109 rowPerm.push_back(std::make_pair(0, basicVars));
110
111 printf(" ");
112 for (int i = 0; i < nonbasicVars; i++) {
113 int l = colPerm[i + 1].second - 1;
114 printf(" x%d ", nonbasicVar[l]);
115 }
116 printf("\n");
117 for (int j = 0; j <= basicVars; j++) {
118 int k = rowPerm[j].second;
119 if (j < basicVars) {
120 printf("x%d = ", basicVar[k]);
121 }
122 else {
123 printf(" z = ");
124 }
125 for (int i = 0; i < nonbasicVars + 1; i++) {
126 int l = colPerm[i].second;
127 printf("%5.2f ", dictionary[k*stride + l]);
128 }
129 printf("\n");
130 }
131 }
132
133 void LinearProblem::pivot(int j, int i)
134 {
135 // The pivot column will be a mix between the column
136 // of the entering and leaving variable.
137
138 // Store pivot column, set all entries except the one on pivot row to zero
139 // such that these entries will accumulate the contents of the leaving
140 // variable column. The pivot row entry will remain that of the entering
141 // column such that its contents will be spread out into the leaving column's
142 // entries
143 for (int k = 0; k <= basicVars; k++) {
144 pivotColumnTmp[k] = dictionary[k*stride + i];
145 if (k != j) {
146 dictionary[k*stride + i] = 0.0;
147 }
148 }
149
150 // Conceptually make entries on the pivot column to zero by subtracting an
151 // multiple of the pivot row.
152 for (int k = 0; k <= basicVars; k++) {
153 if (k == j) continue;
154 for (int l = 0; l < nonbasicVars + 1; l++) {
155 if (l == i) continue;
156 dictionary[k*stride + l] -= (pivotColumnTmp[k] * dictionary[j*stride + l]) / pivotColumnTmp[j];
157 }
158 // Existing value on the pivot column is in principle minus one (which it
159 // isn't since we retain that value of the entering variable in the pivot column.
160 dictionary[k*stride + i] += pivotColumnTmp[k] / pivotColumnTmp[j];
161 }
162 for (int l = 0; l < nonbasicVars + 1; l++) {
163 if (l == i) continue;
164 // Update pivot row. This is just scaled such that the pivot element becomes -1.
165 dictionary[j*stride + l] = -dictionary[j*stride + l] / pivotColumnTmp[j];
166 }
167 // And finally, set the pivot element to the value of the leaving variable.
168 dictionary[j*stride + i] = 1.0 / pivotColumnTmp[j];
169
170 // And update book-keeping arrays.
171 std::swap(basicVar[j], nonbasicVar[i - 1]);
172 }
173
174 bool LinearProblem::isSolutionFeasible()
175 {
176 for (int k = 0; k < basicVars; k++) {
177 if (dictionary[k*stride] < 0.0) {
178 if (printSteps) prettyPrint("Solution is not feasible");
179 return false;
180 }
181 }
182 return true;
183 }
184
185 int LinearProblem::findPivotColumn()
186 {
187 // Currently greedy. We might have change to Bland's rule.
188
189 int column = -1;
190 double best_val = std::numeric_limits<double>::epsilon();
191 for (int k = 0; k < nonbasicVars; k++) {
192
193 // Weight for steepest edge column rule
194 auto weightDenominator = 1.0;
195
196 bool hasNegativeCoefficient = false;
197 for (int j = 0; j < basicVars; j++) {
198 const auto nonBasicCoefficent = dictionary[j*stride + k + 1];
199 weightDenominator += nonBasicCoefficent*nonBasicCoefficent;
200 hasNegativeCoefficient = hasNegativeCoefficient || (nonBasicCoefficent < -std::numeric_limits<float>::epsilon());
201 }
202
203 if (hasNegativeCoefficient) {
204 auto val = dictionary[basicVars*stride + k + 1] / weightDenominator;
205 if (best_val < val) {
206 column = k + 1;
207 best_val = val;
208 }
209 }
210 }
211
212 // Returns -1 if solution is optimal.
213 return column;
214 }
215
216
217 int LinearProblem::findPivotRow(int pivotColumn)
218 {
219 int pivotRow = -1;
220 double worstRatio = std::numeric_limits<double>::max();
221
222 for (int k = 0; k < basicVars; k++) {
223
224 const auto basicVal = dictionary[k*stride + 0];
225 if (basicVal < -std::numeric_limits<double>::epsilon()) {
226 // We consider this a degeneracy.
227 return -1;
228 }
229
230 const auto nonBasicVal = dictionary[k*stride + pivotColumn];
231 if (nonBasicVal < -std::numeric_limits<double>::epsilon()) {
232 const auto ratio = -basicVal / nonBasicVal;
233 if (ratio < worstRatio) {
234 pivotRow = k;
235 worstRatio = ratio;
236 }
237 }
238 }
239
240 return pivotRow;
241 }
242
243 int LinearProblem::findPivotColumnBland()
244 {
245 int column = -1;
246 int lowest = std::numeric_limits<int>::max();
247 for (int l = 0; l < nonbasicVars; l++) {
248
249 bool hasNegativeCoefficient = false;
250 for (int j = 0; (j < basicVars) && !hasNegativeCoefficient; j++) {
251 hasNegativeCoefficient = dictionary[j*stride + l + 1] < -std::numeric_limits<float>::epsilon();
252 }
253
254 if (hasNegativeCoefficient) {
255 auto i = nonbasicVar[l];
256 auto v = dictionary[basicVars*stride + l + 1];
257 if ((0.0 < v) && (i < lowest)) {
258 lowest = i;
259 column = l + 1;
260 }
261 }
262 }
263 return column;
264 }
265
266 int LinearProblem::findPivotRowBland(int pivotColumn)
267 {
268 int row = -1;
269 int lowest = std::numeric_limits<int>::max();
270 double worstRatio = std::numeric_limits<double>::max();
271
272 for (int k = 0; k < basicVars; k++) {
273 auto j = basicVar[k];
274
275 auto r = dictionary[k*stride + pivotColumn];
276 if (r < -std::numeric_limits<double>::epsilon()) {
277 auto v = dictionary[k*stride + 0];
278 auto ratio = -v / r;
279
280 if ((ratio < worstRatio) || ((ratio == worstRatio) && (j < lowest))) {
281 worstRatio = ratio;
282 lowest = j;
283 row = k;
284 }
285 }
286 }
287 return row;
288 }
289
290
291 void LinearProblem::emitSolution(double* solution)
292 {
293 for (int i = 0; i < basicVars; i++) {
294 auto k = basicVar[i] - 1;
295 if (k < nonbasicVars) {
296 solution[k] = dictionary[i*stride + 0];
297 }
298 }
299 for (int i = 0; i < nonbasicVars; i++) {
300 auto k = nonbasicVar[i] - 1;
301 if (k < nonbasicVars) {
302 solution[k] = 0.0;
303 }
304 }
305 }
306
307 Cogs::Core::SimplexResult LinearProblem::search(int maxIterations)
308 {
309 if(printSteps) prettyPrint("Started search");
310 for (int it = 0; it < maxIterations; it++) {
311 int pivotColumn = findPivotColumn();
312 if (pivotColumn < 0) {
314 }
315
316 int pivotRow = findPivotRow(pivotColumn);
317 if (pivotRow < 0) {
318 if (printSteps) printf("We're stalling, trying Bland's rule.\n");
319
320 // We're stalling, use Bland's rule instead
321 pivotColumn = findPivotColumnBland();
322 if (pivotColumn < 0) {
323 if (printSteps) printf("Failed to find column.\n");
325 }
326 pivotRow = findPivotRowBland(pivotColumn);
327 if (pivotRow < 0) {
328 if (printSteps) printf("Failed to find row.\n");
330 }
331 }
332
333 pivot(pivotRow, pivotColumn);
334 if (printSteps) prettyPrint("Applied pivot");
335 }
337 }
338
339 Cogs::Core::SimplexResult LinearProblem::maximize(double* solution,
340 const int maxIterations)
341 {
342
343 if (isSolutionFeasible()) {
344 if (printSteps) prettyPrint("Initial solution is feasible");
345 }
346 else {
347 if (printSteps) prettyPrint("Initial solution is not feasible");
348
349 // Store objective function
350 for (int l = 0; l < nonbasicVars; l++) {
351 objectiveTmp[l] = dictionary[basicVars*stride + l + 1];
352 }
353
354 // Extend dictionary with nonbasic variable x0 and add new objective function
355 nonbasicVar[nonbasicVars] = 0;
356 nonbasicVars++;
357 for (int k = 0; k < basicVars; k++) {
358 dictionary[k*stride + nonbasicVars] = 1.0;
359 }
360 dictionary[basicVars*stride + nonbasicVars] = -1.0;
361 for (int l = 0; l < nonbasicVars; l++) {
362 dictionary[basicVars*stride + l] = 0.0;
363 }
364
365 if(printSteps) prettyPrint("Extended problem to find feasible solution");
366
367 // Find the 'most infeasible' variable that should leave the basis for x0
368 int leaving = -1;
369 double mostInfeasibleValue = std::numeric_limits<double>::max();
370 for (int k = 0; k < basicVars; k++) {
371 auto value = dictionary[k*stride + 0];
372 if (value < mostInfeasibleValue) {
373 leaving = k;
374 mostInfeasibleValue = value;
375 }
376 }
377 // And swap x0 into the basis
378 pivot(leaving, nonbasicVars);
379 if(printSteps) prettyPrint("Applied pivot");
380
381 auto termination = search(maxIterations);
383 return termination;
384 }
385
386 if (!isSolutionFeasible()) {
388 }
389
390 int x0_index = nonbasicVarIndex(0);
391 if (x0_index < 0) {
392 // x0 is in basis while we reached an optimum,
393 // we conclude that problem is infeasible.
394 if (printSteps) prettyPrint("x0=%d is in basis while we reached an optimum, infeasible solution?");
396 }
397
398 // Remove x0 from dictionary
399 nonbasicVars--;
400 for (int l = x0_index; l < nonbasicVars; l++) {
401 nonbasicVar[l] = nonbasicVar[l + 1];
402 }
403 for (int k = 0; k < basicVars; k++) {
404 for (int l = x0_index; l < nonbasicVars; l++) {
405 dictionary[k*stride + l + 1] = dictionary[k*stride + l + 2];
406 }
407 }
408 assert(nonbasicVarIndex(0) == -1);
409 assert(basicVarIndex(0) == -1);
410
411 // form original objective function in new dictionary
412 dictionary[basicVars*stride + 0] = 0.0;
413 for (int l = 0; l < nonbasicVars; l++) {
414 auto i = nonbasicVar[l] - 1;
415 dictionary[basicVars*stride + l + 1] = i < nonbasicVars ? objectiveTmp[i] : 0.0;
416 }
417
418 for (int k = 0; k < basicVars; k++) {
419 auto j = basicVar[k] - 1;
420 if (j < nonbasicVars) {
421 auto s = objectiveTmp[j];
422 for (int l = 0; l <= nonbasicVars; l++) {
423 dictionary[basicVars*stride + l] += s*dictionary[k*stride + l];
424 }
425 }
426 }
427 }
428
429
430 auto termination = search(maxIterations);
432 emitSolution(solution);
433 }
434 return termination;
435 }
436
437}
438
439
440size_t Cogs::Core::simplexScratchSize(const size_t variableCount,
441 const size_t /*objectiveCount*/,
442 const size_t restrictionCount)
443{
444 return
445 sizeof(int)*(restrictionCount) + // basicVar
446 sizeof(int)*(variableCount + 1) + // nonbasicVar
447 sizeof(double)*(variableCount + 2)*(restrictionCount + 1) + // dictionary
448 sizeof(double)*(restrictionCount + 1) + // pivotColumnTmp
449 sizeof(double)*(variableCount) + 8; // objectiveTmp
450}
451
453 uintptr_t scratch,
454 double* solutions,
455 const int variableCount,
456 const double* objectives,
457 const int objectiveCount,
458 const double* restrictions,
459 const int restrictionCount,
460 const int maxIterationCount,
461 const bool printSteps)
462{
463 //std::vector<int> basicVar(restrictionCount);
464 //std::vector<int> nonbasicVar(variableCount+1);
465 //std::vector<double> dictionary((variableCount + 2)*(restrictionCount + 1));
466 //std::vector<double> pivotColumnTmp(restrictionCount+1);
467 //std::vector<double> objectiveTmp(variableCount);
468
469 LinearProblem problem;
470 problem.nonbasicVars = variableCount;
471 problem.basicVars = restrictionCount;
472 problem.stride = variableCount + 2; // one for constant, and one if we must expand problem for initial solution.
473
474 // align scratch to 8 byte boundary (double) by finding the first pointer larger or equal to scratch which is a multiple of 8 :
475 scratch = (scratch + 7u) & ~uintptr_t(7); //add 7 and then set the last 3-bits to 0
476 problem.dictionary = reinterpret_cast<double*>(scratch); scratch += sizeof(double)*(variableCount + 2)*(restrictionCount + 1); //dictionary.data();
477 problem.pivotColumnTmp = reinterpret_cast<double*>(scratch); scratch += sizeof(double)*(restrictionCount + 1); //pivotColumnTmp.data();
478 problem.objectiveTmp = reinterpret_cast<double*>(scratch); scratch += sizeof(double)*(variableCount); // objectiveTmp.data();
479 problem.basicVar = reinterpret_cast<int*>(scratch); scratch += sizeof(int)*(restrictionCount); //basicVar.data();
480 problem.nonbasicVar = reinterpret_cast<int*>(scratch); scratch += sizeof(int)*(variableCount + 1); //nonbasicVar.data();
481 problem.printSteps = printSteps;
482
483
484 // Do it brute force for now, not recycling initial feasible solution.
485 SimplexResult rv = SimplexResult::OptimalSolutionFound;
486 for (int i = 0; i < objectiveCount && (rv == SimplexResult::OptimalSolutionFound); i++) {
487 problem.initializeDictionary(objectives + i*variableCount,
488 restrictions);
489 rv = problem.maximize(solutions + i*variableCount,
490 maxIterationCount);
491 }
492 return rv;
493}
A Context instance contains all the services, systems and runtime components needed to use Cogs.
Definition: Context.h:83
COGSCORE_DLL_API SimplexResult simplexMaximize(Context *context, uintptr_t scratch, double *solutions, const int variableCount, const double *objectives, const int objectiveCount, const double *restrictions, const int restrictionCount, const int maxIterationCount, const bool printSteps=false)
Find the maximum of a linear programming problem using the simplex method.
Definition: Simplex.cpp:452
SimplexResult
Definition: Simplex.h:12
@ OptimalSolutionFound
Success, optimal solution was found and written to solution.
@ NoFeasibleSolutions
Success, determined that no feasible solutions exist.
@ TooManyIterations
Failure, ran more iterations than allowed.
@ BlandsFailed
Failure while applying Bland's rule.
COGSCORE_DLL_API size_t simplexScratchSize(const size_t variableCount, const size_t objectiveCount, const size_t restrictionCount)
Number of bytes of scratch array required by simplexMaximize.
Definition: Simplex.cpp:440