diff options
author | Jesus Sanchez-Palencia <jesus.palencia@openbossa.org> | 2009-05-28 17:42:36 (GMT) |
---|---|---|
committer | Eduardo M. Fleury <eduardo.fleury@openbossa.org> | 2009-07-22 18:03:52 (GMT) |
commit | 26a785e3c65a5f60bcf5bb2e07e58bc6b544dc30 (patch) | |
tree | 88a61bdcb24206534f2a27ae1c1f593074747444 | |
parent | a3b7580944d22b4f3f59bc6a4828a7b01a26407f (diff) | |
download | Qt-26a785e3c65a5f60bcf5bb2e07e58bc6b544dc30.zip Qt-26a785e3c65a5f60bcf5bb2e07e58bc6b544dc30.tar.gz Qt-26a785e3c65a5f60bcf5bb2e07e58bc6b544dc30.tar.bz2 |
QSimplex: Adding the Simplex solver for linear systems
This implementation is based on the iterative algorithm presented in
http://en.wikibooks.org/wiki/Operations_Research/The_Simplex_Method.
It is capable of giving us the solution to any n variable LP model.
Although we focused on QGraphicsAnchorLayout, the solver is general
enough for being reused in any other Qt classes if needed.
Signed-off-by: Anselmo Lacerda S. de Melo <anselmo.melo@openbossa.org>
Signed-off-by: Caio Marcelo de Oliveira Filho <caio.oliveira@openbossa.org>
Signed-off-by: Eduardo M. Fleury <eduardo.fleury@openbossa.org>
Signed-off-by: Jesus Sanchez-Palencia <jesus.palencia@openbossa.org>
-rw-r--r-- | src/gui/graphicsview/graphicsview.pri | 6 | ||||
-rw-r--r-- | src/gui/graphicsview/qsimplex_p.cpp | 364 | ||||
-rw-r--r-- | src/gui/graphicsview/qsimplex_p.h | 120 |
3 files changed, 488 insertions, 2 deletions
diff --git a/src/gui/graphicsview/graphicsview.pri b/src/gui/graphicsview/graphicsview.pri index ce35721..49ba70b 100644 --- a/src/gui/graphicsview/graphicsview.pri +++ b/src/gui/graphicsview/graphicsview.pri @@ -21,7 +21,8 @@ HEADERS += graphicsview/qgraphicsgridlayout.h \ graphicsview/qgraphicswidget.h \ graphicsview/qgraphicswidget_p.h \ graphicsview/qgridlayoutengine_p.h \ - graphicsview/qgraph_p.h + graphicsview/qgraph_p.h \ + graphicsview/qsimplex_p.h SOURCES += graphicsview/qgraphicsgridlayout.cpp \ graphicsview/qgraphicsitem.cpp \ @@ -40,4 +41,5 @@ SOURCES += graphicsview/qgraphicsgridlayout.cpp \ graphicsview/qgraphicsview.cpp \ graphicsview/qgraphicswidget.cpp \ graphicsview/qgraphicswidget_p.cpp \ - graphicsview/qgridlayoutengine.cpp + graphicsview/qgridlayoutengine.cpp \ + graphicsview/qsimplex_p.cpp diff --git a/src/gui/graphicsview/qsimplex_p.cpp b/src/gui/graphicsview/qsimplex_p.cpp new file mode 100644 index 0000000..4ed11c4 --- /dev/null +++ b/src/gui/graphicsview/qsimplex_p.cpp @@ -0,0 +1,364 @@ +#include "qsimplex_p.h" + +#include <QtCore/qset.h> +#include <QtCore/qdebug.h> + +#include <stdlib.h> + +QSimplex::QSimplex() : rows(0), columns(0), firstArtificial(0), matrix(0) +{ + +} + +QSimplex::~QSimplex() +{ + clearDataStructures(); +} + +void QSimplex::clearDataStructures() +{ + if (matrix == 0) + return; + + // Matrix + rows = 0; + columns = 0; + firstArtificial = 0; + free(matrix); + matrix = 0; + + // Constraints + for (int i = 0; i < constraints.size(); ++i) { + delete constraints[i]->helper.first; + constraints[i]->helper.first = 0; + constraints[i]->helper.second = 0.0; + delete constraints[i]->artificial; + constraints[i]->artificial = 0; + } + constraints.clear(); + + // Other + variables.clear(); + objective = 0; +} + +void QSimplex::setConstraints(const QList<QSimplexConstraint *> newConstraints) +{ + clearDataStructures(); + + if (newConstraints.isEmpty()) + return; + constraints = newConstraints; + + // Set Variables direct mapping + QSet<QSimplexVariable *> variablesSet; + for (int i = 0; i < constraints.size(); ++i) + variablesSet += \ + QSet<QSimplexVariable *>::fromList(constraints[i]->variables.keys()); + variables = variablesSet.toList(); + + // Set Variables reverse mapping + for (int i = 0; i < variables.size(); ++i) { + // The variable "0" goes at the column "1", etc... + variables[i]->index = i + 1; + } + + // Normalize Constraints + int variableIndex = variables.size(); + QList <QSimplexVariable *> artificialList; + + for (int i = 0; i < constraints.size(); ++i) { + QSimplexVariable *slack; + QSimplexVariable *surplus; + QSimplexVariable *artificial; + + Q_ASSERT(constraints[i]->helper.first == 0); + Q_ASSERT(constraints[i]->artificial == 0); + + switch(constraints[i]->ratio) { + case QSimplexConstraint::LessOrEqual: + slack = new QSimplexVariable; + slack->index = ++variableIndex; + constraints[i]->helper.first = slack; + constraints[i]->helper.second = 1.0; + break; + case QSimplexConstraint::MoreOrEqual: + surplus = new QSimplexVariable; + surplus->index = ++variableIndex; + constraints[i]->helper.first = surplus; + constraints[i]->helper.second = -1.0; + // fall through + case QSimplexConstraint::Equal: + artificial = new QSimplexVariable; + constraints[i]->artificial = artificial; + artificialList += constraints[i]->artificial; + break; + } + } + + firstArtificial = variableIndex + 1; + for (int i = 0; i < artificialList.size(); ++i) + artificialList[i]->index = ++variableIndex; + artificialList.clear(); + + // Matrix + + // One for each variable plus the Basic and BFS columns (first and last) + columns = variableIndex + 2; + // One for each constraint plus the objective function + rows = constraints.size() + 1; + + matrix = (qreal *)malloc(sizeof(qreal) * columns * rows); + if (!matrix) { + qWarning() << "QSimplex: Unable to allocate memory!"; + return; + } + for (int i = columns * rows - 1; i >= 0; --i) + matrix[i] = 0.0; + + // Fill Matrix + for (int i = 1; i <= constraints.size(); ++i) { + QSimplexConstraint *c = constraints[i - 1]; + + if (c->artificial) { + // Will use artificial basic variable + setValueAt(i, 0, c->artificial->index); + setValueAt(i, c->artificial->index, 1.0); + + if (c->helper.second != 0.0) { + // Surplus variable + setValueAt(i, c->helper.first->index, c->helper.second); + } + } else { + // Slack is used as the basic variable + Q_ASSERT(c->helper.second == 1.0); + setValueAt(i, 0, c->helper.first->index); + setValueAt(i, c->helper.first->index, 1.0); + } + + QHash<QSimplexVariable *, qreal>::const_iterator iter; + for (iter = c->variables.constBegin(); + iter != c->variables.constEnd(); + ++iter) { + setValueAt(i, iter.key()->index, iter.value()); + } + + setValueAt(i, columns - 1, c->constant); + } + + // Set temporary objective: -1 * sum_of_artificial_vars + for (int j = firstArtificial; j < columns - 1; ++j) + setValueAt(0, j, 1.0); + + // Maximize our objective (artificial vars go to zero) + solveMaxHelper(); + + if (valueAt(0, columns - 1) != 0.0) { + qWarning() << "QSimplex: No feaseable solution!"; + clearDataStructures(); + return; + } + + // Remove artificial variables + clearColumns(firstArtificial, columns - 2); +} + +void QSimplex::solveMaxHelper() +{ + reducedRowEchelon(); + while (iterate()) ; +} + +void QSimplex::setObjective(QSimplexConstraint *newObjective) +{ + objective = newObjective; +} + +void QSimplex::clearRow(int rowIndex) +{ + qreal *item = matrix + rowIndex * columns; + for (int i = 0; i < columns; ++i) + item[i] = 0.0; +} + +void QSimplex::clearColumns(int first, int last) +{ + for (int i = 0; i < rows; ++i) { + qreal *row = matrix + i * columns; + for (int j = first; j <= last; ++j) + row[j] = 0.0; + } +} + +void QSimplex::dumpMatrix() +{ + printf("---- Simplex Matrix ----\n"); + + printf(" "); + for (int j = 0; j < columns; ++j) + printf(" <% 2d >", j); + printf("\n"); + + for (int i = 0; i < rows; ++i) { + printf("Row %2d:", i); + + qreal *row = matrix + i * columns; + for (int j = 0; j < columns; ++j) { + printf(" % 2.2f", row[j]); + } + printf("\n"); + } + printf("------------------------\n\n"); +} + +void QSimplex::combineRows(int toIndex, int fromIndex, qreal factor) +{ + if (!factor) + return; + + qreal *from = matrix + fromIndex * columns; + qreal *to = matrix + toIndex * columns; + + for (int j = 1; j < columns; ++j) { + to[j] += factor * from[j]; + + // ### Avoid Numerical errors + if (qAbs(to[j]) < 0.0000000001) + to[j] = 0.0; + } +} + +int QSimplex::findPivotColumn() +{ + qreal min = 0; + int minIndex = -1; + + for (int j = 0; j < columns-1; ++j) { + if (valueAt(0, j) < min) { + min = valueAt(0, j); + minIndex = j; + } + } + + return minIndex; +} + +int QSimplex::pivotRowForColumn(int column) +{ + qreal min = 999999999999.0; // ### + int minIndex = -1; + + for (int i = 1; i < rows; ++i) { + qreal divisor = valueAt(i, column); + if (divisor <= 0) + continue; + + qreal quotient = valueAt(i, columns - 1) / divisor; + if (quotient < min) { + min = quotient; + minIndex = i; + } + } + + return minIndex; +} + +void QSimplex::reducedRowEchelon() +{ + for (int i = 1; i < rows; ++i) { + int factorInObjectiveRow = valueAt(i, 0); + combineRows(0, i, -1 * valueAt(0, factorInObjectiveRow)); + } +} + +bool QSimplex::iterate() +{ + // Find Pivot column + int pivotColumn = findPivotColumn(); + if (pivotColumn == -1) + return false; + + // Find Pivot row for column + int pivotRow = pivotRowForColumn(pivotColumn); + if (pivotRow == -1) { + qWarning() << "QSimplex: Unbounded problem!"; + return false; + } + + // Normalize Pivot Row + qreal pivot = valueAt(pivotRow, pivotColumn); + if (pivot != 1.0) + combineRows(pivotRow, pivotRow, (1.0 - pivot) / pivot); + + // Update other rows + for (int row=0; row < rows; ++row) { + if (row == pivotRow) + continue; + + combineRows(row, pivotRow, -1 * valueAt(row, pivotColumn)); + } + + // Update first column + setValueAt(pivotRow, 0, pivotColumn); + + // dumpMatrix(); + // printf("------------ end of iteration --------------\n"); + return true; +} + +qreal QSimplex::solveMin() +{ + // Remove old objective + clearRow(0); + + // Set new objective + QHash<QSimplexVariable *, qreal>::const_iterator iter; + for (iter = objective->variables.constBegin(); + iter != objective->variables.constEnd(); + ++iter) { + setValueAt(0, iter.key()->index, iter.value()); + } + + solveMaxHelper(); + collectResults(); + + return -1 * valueAt(0, columns - 1); +} + +qreal QSimplex::solveMax() +{ + // Remove old objective + clearRow(0); + + // Set new objective + QHash<QSimplexVariable *, qreal>::const_iterator iter; + for (iter = objective->variables.constBegin(); + iter != objective->variables.constEnd(); + ++iter) { + setValueAt(0, iter.key()->index, -1 * iter.value()); + } + + solveMaxHelper(); + collectResults(); + + return valueAt(0, columns - 1); +} + +void QSimplex::collectResults() +{ + // All variables are zero unless overridden below. + + // ### Is this really needed? Is there any chance that an + // important variable remains as non-basic at the end of simplex? + for (int i = 0; i < variables.size(); ++i) + variables[i]->result = 0; + + // Basic variables + // Update the variable indicated in the first column with the value + // in the last column. + for (int i = 1; i < rows; ++i) { + int index = valueAt(i, 0) - 1; + if (index < variables.size()) + variables[index]->result = valueAt(i, columns - 1); + } +} diff --git a/src/gui/graphicsview/qsimplex_p.h b/src/gui/graphicsview/qsimplex_p.h new file mode 100644 index 0000000..3881893 --- /dev/null +++ b/src/gui/graphicsview/qsimplex_p.h @@ -0,0 +1,120 @@ +/**************************************************************************** +** +** Copyright (C) 1992-$THISYEAR$ $TROLLTECH$. All rights reserved. +** +** This file is part of the $MODULE$ of the Qt Toolkit. +** +** $TROLLTECH_DUAL_LICENSE$ +** +** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +** +****************************************************************************/ + +#ifndef QSIMPLEX_P_H +#define QSIMPLEX_P_H + +#include <QtCore/qhash.h> +#include <QtCore/qpair.h> + +struct QSimplexVariable +{ + QSimplexVariable() : result(0), index(0) {}; + + qreal result; + uint index; +}; + + +/*! + \internal + + Representation of a LP constraint like: + + (c1 * X1) + (c2 * X2) + ... = K + or <= K + or >= K + + Where (ci, Xi) are the pairs in "variables" and K the real "constant". +*/ +struct QSimplexConstraint +{ + QSimplexConstraint() : constant(0), ratio(Equal), artificial(0) {}; + + enum Ratio { + LessOrEqual = 0, + Equal, + MoreOrEqual + }; + + QHash<QSimplexVariable *, qreal> variables; + qreal constant; + Ratio ratio; + + QPair<QSimplexVariable *, qreal> helper; + QSimplexVariable * artificial; +}; + + +class QSimplex +{ +public: + QSimplex(); + virtual ~QSimplex(); + + qreal solveMin(); + qreal solveMax(); + QList<QSimplexVariable *> constraintsVariables(); + + void setConstraints(const QList<QSimplexConstraint *> constraints); + void setObjective(QSimplexConstraint *objective); + + void dumpMatrix(); + +private: + // Matrix handling + qreal valueAt(int row, int column); + void setValueAt(int row, int column, qreal value); + void clearRow(int rowIndex); + void clearColumns(int first, int last); + void combineRows(int toIndex, int fromIndex, qreal factor); + + // Simplex + int findPivotColumn(); + int pivotRowForColumn(int column); + void reducedRowEchelon(); + bool iterate(); + + // Helpers + void clearDataStructures(); + void solveMaxHelper(); + void collectResults(); + + QList<QSimplexConstraint *> constraints; + QList<QSimplexVariable *> variables; + QSimplexConstraint *objective; + + int rows; + int columns; + int firstArtificial; + + qreal *matrix; +}; + +inline QList<QSimplexVariable *> QSimplex::constraintsVariables() +{ + return variables; +} + +inline qreal QSimplex::valueAt(int rowIndex, int columnIndex) +{ + return matrix[rowIndex * columns + columnIndex]; +} + +inline void QSimplex::setValueAt(int rowIndex, int columnIndex, qreal value) +{ + matrix[rowIndex * columns + columnIndex] = value; +} + + +#endif |