Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qsimplex_p.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
5#include "qsimplex_p.h"
6
7#include <QtCore/qset.h>
8#include <QtCore/qdebug.h>
9
10#include <stdlib.h>
11
12QT_BEGIN_NAMESPACE
13
14using namespace Qt::StringLiterals;
15
16/*!
17 \internal
18 \class QSimplex
19
20 The QSimplex class is a Linear Programming problem solver based on the two-phase
21 simplex method.
22
23 It takes a set of QSimplexConstraints as its restrictive constraints and an
24 additional QSimplexConstraint as its objective function. Then methods to maximize
25 and minimize the problem solution are provided.
26
27 The two-phase simplex method is based on the following steps:
28 First phase:
29 1.a) Modify the original, complex, and possibly not feasible problem, into a new,
30 easy to solve problem.
31 1.b) Set as the objective of the new problem, a feasible solution for the original
32 complex problem.
33 1.c) Run simplex to optimize the modified problem and check whether a solution for
34 the original problem exists.
35
36 Second phase:
37 2.a) Go back to the original problem with the feasibl (but not optimal) solution
38 found in the first phase.
39 2.b) Set the original objective.
40 3.c) Run simplex to optimize the original problem towards its optimal solution.
41*/
42
43/*!
44 \internal
45*/
46QSimplex::QSimplex() : objective(nullptr), rows(0), columns(0), firstArtificial(0), matrix(nullptr)
47{
48}
49
50/*!
51 \internal
52*/
54{
55 clearDataStructures();
56}
57
58/*!
59 \internal
60*/
61void QSimplex::clearDataStructures()
62{
63 if (matrix == nullptr)
64 return;
65
66 // Matrix
67 rows = 0;
68 columns = 0;
69 firstArtificial = 0;
70 free(matrix);
71 matrix = nullptr;
72
73 // Constraints
74 for (int i = 0; i < constraints.size(); ++i) {
75 delete constraints[i]->helper.first;
76 delete constraints[i]->artificial;
77 delete constraints[i];
78 }
79 constraints.clear();
80
81 // Other
82 variables.clear();
83 objective = nullptr;
84}
85
86/*!
87 \internal
88 Sets the new constraints in the simplex solver and returns whether the problem
89 is feasible.
90
91 This method sets the new constraints, normalizes them, creates the simplex matrix
92 and runs the first simplex phase.
93*/
94bool QSimplex::setConstraints(const QList<QSimplexConstraint *> &newConstraints)
95{
96 ////////////////////////////
97 // Reset to initial state //
98 ////////////////////////////
99 clearDataStructures();
100
101 if (newConstraints.isEmpty())
102 return true; // we are ok with no constraints
103
104 // Make deep copy of constraints. We need this copy because we may change
105 // them in the simplification method.
106 for (int i = 0; i < newConstraints.size(); ++i) {
108 c->constant = newConstraints[i]->constant;
109 c->ratio = newConstraints[i]->ratio;
110 c->variables = newConstraints[i]->variables;
111 constraints << c;
112 }
113
114 // Remove constraints of type Var == K and replace them for their value.
115 if (!simplifyConstraints(&constraints)) {
116 qWarning("QSimplex: No feasible solution!");
117 clearDataStructures();
118 return false;
119 }
120
121 ///////////////////////////////////////
122 // Prepare variables and constraints //
123 ///////////////////////////////////////
124
125 // Set Variables direct mapping.
126 // "variables" is a list that provides a stable, indexed list of all variables
127 // used in this problem.
128 QSet<QSimplexVariable *> variablesSet;
129 for (int i = 0; i < constraints.size(); ++i) {
130 const auto &v = constraints.at(i)->variables;
131 for (auto it = v.cbegin(), end = v.cend(); it != end; ++it)
132 variablesSet.insert(it.key());
133 }
134 variables = variablesSet.values();
135
136 // Set Variables reverse mapping
137 // We also need to be able to find the index for a given variable, to do that
138 // we store in each variable its index.
139 for (int i = 0; i < variables.size(); ++i) {
140 // The variable "0" goes at the column "1", etc...
141 variables[i]->index = i + 1;
142 }
143
144 // Normalize Constraints
145 // In this step, we prepare the constraints in two ways:
146 // Firstly, we modify all constraints of type "LessOrEqual" or "MoreOrEqual"
147 // by the adding slack or surplus variables and making them "Equal" constraints.
148 // Secondly, we need every single constraint to have a direct, easy feasible
149 // solution. Constraints that have slack variables are already easy to solve,
150 // to all the others we add artificial variables.
151 //
152 // At the end we modify the constraints as follows:
153 // - LessOrEqual: SLACK variable is added.
154 // - Equal: ARTIFICIAL variable is added.
155 // - More or Equal: ARTIFICIAL and SURPLUS variables are added.
156 int variableIndex = variables.size();
157 QList <QSimplexVariable *> artificialList;
158
159 for (int i = 0; i < constraints.size(); ++i) {
160 QConcreteSimplexVariable *slack;
161 QConcreteSimplexVariable *surplus;
162 QConcreteSimplexVariable *artificial;
163
164 Q_ASSERT(constraints[i]->helper.first == 0);
165 Q_ASSERT(constraints[i]->artificial == nullptr);
166
167 switch(constraints[i]->ratio) {
168 case QSimplexConstraint::LessOrEqual:
169 slack = new QConcreteSimplexVariable;
170 slack->index = ++variableIndex;
171 constraints[i]->helper.first = slack;
172 constraints[i]->helper.second = 1.0;
173 break;
174 case QSimplexConstraint::MoreOrEqual:
175 surplus = new QConcreteSimplexVariable;
176 surplus->index = ++variableIndex;
177 constraints[i]->helper.first = surplus;
178 constraints[i]->helper.second = -1.0;
179 Q_FALLTHROUGH();
180 case QSimplexConstraint::Equal:
181 artificial = new QConcreteSimplexVariable;
182 constraints[i]->artificial = artificial;
183 artificialList += constraints[i]->artificial;
184 break;
185 }
186 }
187
188 // All original, slack and surplus have already had its index set
189 // at this point. We now set the index of the artificial variables
190 // as to ensure they are at the end of the variable list and therefore
191 // can be easily removed at the end of this method.
192 firstArtificial = variableIndex + 1;
193 for (int i = 0; i < artificialList.size(); ++i)
194 artificialList[i]->index = ++variableIndex;
195 artificialList.clear();
196
197 /////////////////////////////
198 // Fill the Simplex matrix //
199 /////////////////////////////
200
201 // One for each variable plus the Basic and BFS columns (first and last)
202 columns = variableIndex + 2;
203 // One for each constraint plus the objective function
204 rows = constraints.size() + 1;
205
206 matrix = (qreal *)malloc(sizeof(qreal) * columns * rows);
207 if (!matrix) {
208 qWarning("QSimplex: Unable to allocate memory!");
209 return false;
210 }
211 for (int i = columns * rows - 1; i >= 0; --i)
212 matrix[i] = 0.0;
213
214 // Fill Matrix
215 for (int i = 1; i <= constraints.size(); ++i) {
216 QSimplexConstraint *c = constraints[i - 1];
217
218 if (c->artificial) {
219 // Will use artificial basic variable
220 setValueAt(i, 0, c->artificial->index);
221 setValueAt(i, c->artificial->index, 1.0);
222
223 if (c->helper.second != 0.0) {
224 // Surplus variable
225 setValueAt(i, c->helper.first->index, c->helper.second);
226 }
227 } else {
228 // Slack is used as the basic variable
229 Q_ASSERT(c->helper.second == 1.0);
230 setValueAt(i, 0, c->helper.first->index);
231 setValueAt(i, c->helper.first->index, 1.0);
232 }
233
234 for (auto iter = c->variables.cbegin(); iter != c->variables.cend(); ++iter)
235 setValueAt(i, iter.key()->index, iter.value());
236
237 setValueAt(i, columns - 1, c->constant);
238 }
239
240 // Set objective for the first-phase Simplex.
241 // Z = -1 * sum_of_artificial_vars
242 for (int j = firstArtificial; j < columns - 1; ++j)
243 setValueAt(0, j, 1.0);
244
245 // Maximize our objective (artificial vars go to zero)
246 solveMaxHelper();
247
248 // If there is a solution where the sum of all artificial
249 // variables is zero, then all of them can be removed and yet
250 // we will have a feasible (but not optimal) solution for the
251 // original problem.
252 // Otherwise, we clean up our structures and report there is
253 // no feasible solution.
254 if ((valueAt(0, columns - 1) != 0.0) && (qAbs(valueAt(0, columns - 1)) > 0.00001)) {
255 qWarning("QSimplex: No feasible solution!");
256 clearDataStructures();
257 return false;
258 }
259
260 // Remove artificial variables. We already have a feasible
261 // solution for the first problem, thus we don't need them
262 // anymore.
263 clearColumns(firstArtificial, columns - 2);
264
265 return true;
266}
267
268/*!
269 \internal
270
271 Run simplex on the current matrix with the current objective.
272
273 This is the iterative method. The matrix lines are combined
274 as to modify the variable values towards the best solution possible.
275 The method returns when the matrix is in the optimal state.
276*/
277void QSimplex::solveMaxHelper()
278{
279 reducedRowEchelon();
280 while (iterate()) ;
281}
282
283/*!
284 \internal
285*/
287{
288 objective = newObjective;
289}
290
291/*!
292 \internal
293*/
294void QSimplex::clearRow(int rowIndex)
295{
296 qreal *item = matrix + rowIndex * columns;
297 for (int i = 0; i < columns; ++i)
298 item[i] = 0.0;
299}
300
301/*!
302 \internal
303*/
304void QSimplex::clearColumns(int first, int last)
305{
306 for (int i = 0; i < rows; ++i) {
307 qreal *row = matrix + i * columns;
308 for (int j = first; j <= last; ++j)
309 row[j] = 0.0;
310 }
311}
312
313/*!
314 \internal
315*/
317{
318 qDebug("---- Simplex Matrix ----\n");
319
320 QString str(" "_L1);
321 for (int j = 0; j < columns; ++j)
322 str += QString::fromLatin1(" <%1 >").arg(j, 2);
323 qDebug("%s", qPrintable(str));
324 for (int i = 0; i < rows; ++i) {
325 str = QString::fromLatin1("Row %1:").arg(i, 2);
326
327 qreal *row = matrix + i * columns;
328 for (int j = 0; j < columns; ++j)
329 str += QString::fromLatin1("%1").arg(row[j], 7, 'f', 2);
330 qDebug("%s", qPrintable(str));
331 }
332 qDebug("------------------------\n");
333}
334
335/*!
336 \internal
337*/
338void QSimplex::combineRows(int toIndex, int fromIndex, qreal factor)
339{
340 if (!factor)
341 return;
342
343 qreal *from = matrix + fromIndex * columns;
344 qreal *to = matrix + toIndex * columns;
345
346 for (int j = 1; j < columns; ++j) {
347 qreal value = from[j];
348
349 // skip to[j] = to[j] + factor*0.0
350 if (value == 0.0)
351 continue;
352
353 to[j] += factor * value;
354
355 // ### Avoid Numerical errors
356 if (qAbs(to[j]) < 0.0000000001)
357 to[j] = 0.0;
358 }
359}
360
361/*!
362 \internal
363*/
364int QSimplex::findPivotColumn()
365{
366 qreal min = 0;
367 int minIndex = -1;
368
369 for (int j = 0; j < columns-1; ++j) {
370 if (valueAt(0, j) < min) {
371 min = valueAt(0, j);
372 minIndex = j;
373 }
374 }
375
376 return minIndex;
377}
378
379/*!
380 \internal
381
382 For a given pivot column, find the pivot row. That is, the row with the
383 minimum associated "quotient" where:
384
385 - quotient is the division of the value in the last column by the value
386 in the pivot column.
387 - rows with value less or equal to zero are ignored
388 - if two rows have the same quotient, lines are chosen based on the
389 highest variable index (value in the first column)
390
391 The last condition avoids a bug where artificial variables would be
392 left behind for the second-phase simplex, and with 'good'
393 constraints would be removed before it, what would lead to incorrect
394 results.
395*/
396int QSimplex::pivotRowForColumn(int column)
397{
398 qreal min = qreal(999999999999.0); // ###
399 int minIndex = -1;
400
401 for (int i = 1; i < rows; ++i) {
402 qreal divisor = valueAt(i, column);
403 if (divisor <= 0)
404 continue;
405
406 qreal quotient = valueAt(i, columns - 1) / divisor;
407 if (quotient < min) {
408 min = quotient;
409 minIndex = i;
410 } else if ((quotient == min) && (valueAt(i, 0) > valueAt(minIndex, 0))) {
411 minIndex = i;
412 }
413 }
414
415 return minIndex;
416}
417
418/*!
419 \internal
420*/
421void QSimplex::reducedRowEchelon()
422{
423 for (int i = 1; i < rows; ++i) {
424 int factorInObjectiveRow = valueAt(i, 0);
425 combineRows(0, i, -1 * valueAt(0, factorInObjectiveRow));
426 }
427}
428
429/*!
430 \internal
431
432 Does one iteration towards a better solution for the problem.
433 See 'solveMaxHelper'.
434*/
435bool QSimplex::iterate()
436{
437 // Find Pivot column
438 int pivotColumn = findPivotColumn();
439 if (pivotColumn == -1)
440 return false;
441
442 // Find Pivot row for column
443 int pivotRow = pivotRowForColumn(pivotColumn);
444 if (pivotRow == -1) {
445 qWarning("QSimplex: Unbounded problem!");
446 return false;
447 }
448
449 // Normalize Pivot Row
450 qreal pivot = valueAt(pivotRow, pivotColumn);
451 if (pivot != 1.0)
452 combineRows(pivotRow, pivotRow, (1.0 - pivot) / pivot);
453
454 // Update other rows
455 for (int row=0; row < rows; ++row) {
456 if (row == pivotRow)
457 continue;
458
459 combineRows(row, pivotRow, -1 * valueAt(row, pivotColumn));
460 }
461
462 // Update first column
463 setValueAt(pivotRow, 0, pivotColumn);
464
465 // dumpMatrix();
466 // qDebug("------------ end of iteration --------------\n");
467 return true;
468}
469
470/*!
471 \internal
472
473 Both solveMin and solveMax are interfaces to this method.
474
475 The enum SolverFactor admits 2 values: Minimum (-1) and Maximum (+1).
476
477 This method sets the original objective and runs the second phase
478 Simplex to obtain the optimal solution for the problem. As the internal
479 simplex solver is only able to _maximize_ objectives, we handle the
480 minimization case by inverting the original objective and then
481 maximizing it.
482*/
483qreal QSimplex::solver(SolverFactor factor)
484{
485 // Remove old objective
486 clearRow(0);
487
488 // Set new objective in the first row of the simplex matrix
489 qreal resultOffset = 0;
490 for (auto iter = objective->variables.cbegin(); iter != objective->variables.cend(); ++iter) {
491
492 // Check if the variable was removed in the simplification process.
493 // If so, we save its offset to the objective function and skip adding
494 // it to the matrix.
495 if (iter.key()->index == -1) {
496 resultOffset += iter.value() * iter.key()->result;
497 continue;
498 }
499
500 setValueAt(0, iter.key()->index, -1 * factor * iter.value());
501 }
502
503 solveMaxHelper();
504 collectResults();
505
506#ifdef QT_DEBUG
507 for (int i = 0; i < constraints.size(); ++i) {
508 Q_ASSERT(constraints[i]->isSatisfied());
509 }
510#endif
511
512 // Return the value calculated by the simplex plus the value of the
513 // fixed variables.
514 return (qToUnderlying(factor) * valueAt(0, columns - 1)) + resultOffset;
515}
516
517/*!
518 \internal
519 Minimize the original objective.
520*/
522{
523 return solver(Minimum);
524}
525
526/*!
527 \internal
528 Maximize the original objective.
529*/
531{
532 return solver(Maximum);
533}
534
535/*!
536 \internal
537
538 Reads results from the simplified matrix and saves them in the
539 "result" member of each QSimplexVariable.
540*/
541void QSimplex::collectResults()
542{
543 // All variables are zero unless overridden below.
544
545 // ### Is this really needed? Is there any chance that an
546 // important variable remains as non-basic at the end of simplex?
547 for (int i = 0; i < variables.size(); ++i)
548 variables[i]->result = 0;
549
550 // Basic variables
551 // Update the variable indicated in the first column with the value
552 // in the last column.
553 for (int i = 1; i < rows; ++i) {
554 int index = valueAt(i, 0) - 1;
555 if (index < variables.size())
556 variables[index]->result = valueAt(i, columns - 1);
557 }
558}
559
560/*!
561 \internal
562
563 Looks for single-valued variables and remove them from the constraints list.
564*/
565bool QSimplex::simplifyConstraints(QList<QSimplexConstraint *> *constraints)
566{
567 QHash<QSimplexVariable *, qreal> results; // List of single-valued variables
568 bool modified = true; // Any chance more optimization exists?
569
570 while (modified) {
571 modified = false;
572
573 // For all constraints
574 QList<QSimplexConstraint *>::iterator iter = constraints->begin();
575 while (iter != constraints->end()) {
576 QSimplexConstraint *c = *iter;
577 if ((c->ratio == QSimplexConstraint::Equal) && (c->variables.size() == 1)) {
578 // Check whether this is a constraint of type Var == K
579 // If so, save its value to "results".
580 QSimplexVariable *variable = c->variables.constBegin().key();
581 qreal result = c->constant / c->variables.value(variable);
582
583 results.insert(variable, result);
584 variable->result = result;
585 variable->index = -1;
586 modified = true;
587
588 }
589
590 // Replace known values among their variables
591 for (auto r = results.cbegin(); r != results.cend(); ++r) {
592 if (c->variables.contains(r.key())) {
593 c->constant -= r.value() * c->variables.take(r.key());
594 modified = true;
595 }
596 }
597
598 // Keep it normalized
599 if (c->constant < 0)
600 c->invert();
601
602 if (c->variables.isEmpty()) {
603 // If constraint became empty due to substitution, delete it.
604 if (c->isSatisfied() == false)
605 // We must ensure that the constraint soon to be deleted would not
606 // make the problem unfeasible if left behind. If that's the case,
607 // we return false so the simplex solver can properly report that.
608 return false;
609
610 delete c;
611 iter = constraints->erase(iter);
612 } else {
613 ++iter;
614 }
615 }
616 }
617
618 return true;
619}
620
622{
623 constant = -constant;
624 ratio = Ratio(2 - ratio);
625
626 for (auto iter = variables.begin(); iter != variables.end(); ++iter)
627 iter.value() = -iter.value();
628}
629
630QT_END_NAMESPACE
bool setConstraints(const QList< QSimplexConstraint * > &constraints)
void dumpMatrix()
void setObjective(QSimplexConstraint *objective)
qreal solveMax()
qreal solveMin()
QConcreteSimplexVariable * artificial
Definition qsimplex_p.h:70