applied Benjamin's patch from 2009-09-08

warning: I had to update the expected matrix in SimplexTableauTest.testdiscardArtificialVariables

JIRA: MATH-286

git-svn-id: https://svn.apache.org/repos/asf/commons/proper/math/trunk@812831 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Luc Maisonobe 2009-09-09 08:48:03 +00:00
parent e5a77a7651
commit aa91ffbe33
4 changed files with 70 additions and 80 deletions

View File

@ -174,7 +174,7 @@ public class SimplexSolver extends AbstractLinearOptimizer {
new SimplexTableau(function, linearConstraints, goal, nonNegative, epsilon);
solvePhase1(tableau);
tableau.discardArtificialVariables();
tableau.dropPhase1Objective();
while (!tableau.isOptimal()) {
doIteration(tableau);

View File

@ -112,8 +112,7 @@ class SimplexTableau implements Serializable {
getConstraintTypeCounts(Relationship.GEQ);
this.numArtificialVariables = getConstraintTypeCounts(Relationship.EQ) +
getConstraintTypeCounts(Relationship.GEQ);
this.tableau = new Array2DRowRealMatrix(createTableau(goalType == GoalType.MAXIMIZE));
initialize();
this.tableau = createTableau(goalType == GoalType.MAXIMIZE);
}
/**
@ -121,29 +120,29 @@ class SimplexTableau implements Serializable {
* @param maximize if true, goal is to maximize the objective function
* @return created tableau
*/
protected double[][] createTableau(final boolean maximize) {
protected RealMatrix createTableau(final boolean maximize) {
// create a matrix of the correct size
int width = numDecisionVariables + numSlackVariables +
numArtificialVariables + getNumObjectiveFunctions() + 1; // + 1 is for RHS
int height = constraints.size() + getNumObjectiveFunctions();
double[][] matrix = new double[height][width];
Array2DRowRealMatrix matrix = new Array2DRowRealMatrix(height, width);
// initialize the objective function rows
if (getNumObjectiveFunctions() == 2) {
matrix[0][0] = -1;
matrix.setEntry(0, 0, -1);
}
int zIndex = (getNumObjectiveFunctions() == 1) ? 0 : 1;
matrix[zIndex][zIndex] = maximize ? 1 : -1;
matrix.setEntry(zIndex, zIndex, maximize ? 1 : -1);
RealVector objectiveCoefficients =
maximize ? f.getCoefficients().mapMultiply(-1) : f.getCoefficients();
copyArray(objectiveCoefficients.getData(), matrix[zIndex]);
matrix[zIndex][width - 1] =
maximize ? f.getConstantTerm() : -1 * f.getConstantTerm();
copyArray(objectiveCoefficients.getData(), matrix.getDataRef()[zIndex]);
matrix.setEntry(zIndex, width - 1,
maximize ? f.getConstantTerm() : -1 * f.getConstantTerm());
if (!restrictToNonNegative) {
matrix[zIndex][getSlackVariableOffset() - 1] =
getInvertedCoeffiecientSum(objectiveCoefficients);
matrix.setEntry(zIndex, getSlackVariableOffset() - 1,
getInvertedCoeffiecientSum(objectiveCoefficients));
}
// initialize the constraint rows
@ -154,34 +153,34 @@ class SimplexTableau implements Serializable {
int row = getNumObjectiveFunctions() + i;
// decision variable coefficients
copyArray(constraint.getCoefficients().getData(), matrix[row]);
copyArray(constraint.getCoefficients().getData(), matrix.getDataRef()[row]);
// x-
if (!restrictToNonNegative) {
matrix[row][getSlackVariableOffset() - 1] =
getInvertedCoeffiecientSum(constraint.getCoefficients());
matrix.setEntry(row, getSlackVariableOffset() - 1,
getInvertedCoeffiecientSum(constraint.getCoefficients()));
}
// RHS
matrix[row][width - 1] = constraint.getValue();
matrix.setEntry(row, width - 1, constraint.getValue());
// slack variables
if (constraint.getRelationship() == Relationship.LEQ) {
matrix[row][getSlackVariableOffset() + slackVar++] = 1; // slack
matrix.setEntry(row, getSlackVariableOffset() + slackVar++, 1); // slack
} else if (constraint.getRelationship() == Relationship.GEQ) {
matrix[row][getSlackVariableOffset() + slackVar++] = -1; // excess
matrix.setEntry(row, getSlackVariableOffset() + slackVar++, -1); // excess
}
// artificial variables
if ((constraint.getRelationship() == Relationship.EQ) ||
(constraint.getRelationship() == Relationship.GEQ)) {
matrix[0][getArtificialVariableOffset() + artificialVar] = 1;
matrix[row][getArtificialVariableOffset() + artificialVar++] = 1;
matrix.setEntry(0, getArtificialVariableOffset() + artificialVar, 1);
matrix.setEntry(row, getArtificialVariableOffset() + artificialVar++, 1);
matrix.setRowVector(0, matrix.getRowVector(0).subtract(matrix.getRowVector(row)));
}
}
return matrix;
}
/**
@ -235,17 +234,6 @@ class SimplexTableau implements Serializable {
return count;
}
/**
* Puts the tableau in proper form by zeroing out the artificial variables
* in the objective function via elementary row operations.
*/
private void initialize() {
for (int artificialVar = 0; artificialVar < numArtificialVariables; artificialVar++) {
int row = getBasicRow(getArtificialVariableOffset() + artificialVar);
subtractRow(0, row, 1.0);
}
}
/**
* Get the -1 times the sum of all coefficients in the given array.
* @param coefficients coefficients to sum
@ -264,30 +252,9 @@ class SimplexTableau implements Serializable {
* @param col index of the column to check
* @return the row that the variable is basic in. null if the column is not basic
*/
Integer getBasicRow(final int col) {
return getBasicRow(col, true);
}
/**
* Checks whether the given column is basic.
* @param col index of the column to check
* @return the row that the variable is basic in. null if the column is not basic
*/
private Integer getBasicRowForSolution(final int col) {
return getBasicRow(col, false);
}
/**
* Checks whether the given column is basic.
* @param col index of the column to check
* @param ignoreObjectiveRows if true ignore the first rows which correspond
* to objective functions
* @return the row that the variable is basic in. null if the column is not basic
*/
private Integer getBasicRow(final int col, boolean ignoreObjectiveRows) {
protected Integer getBasicRow(final int col) {
Integer row = null;
int start = ignoreObjectiveRows ? getNumObjectiveFunctions() : 0;
for (int i = start; i < getHeight(); i++) {
for (int i = 0; i < getHeight(); i++) {
if (MathUtils.equals(getEntry(i, col), 1.0, epsilon) && (row == null)) {
row = i;
} else if (!MathUtils.equals(getEntry(i, col), 0.0, epsilon)) {
@ -298,21 +265,42 @@ class SimplexTableau implements Serializable {
}
/**
* Removes the phase 1 objective function and artificial variables from this tableau.
* Removes the phase 1 objective function, positive cost non-artificial variables,
* and the non-basic artificial variables from this tableau.
*/
protected void discardArtificialVariables() {
if (numArtificialVariables == 0) {
protected void dropPhase1Objective() {
if (getNumObjectiveFunctions() == 1) {
return;
}
int width = getWidth() - numArtificialVariables - 1;
int height = getHeight() - 1;
double[][] matrix = new double[height][width];
for (int i = 0; i < height; i++) {
for (int j = 0; j < width - 1; j++) {
matrix[i][j] = getEntry(i + 1, j + 1);
}
matrix[i][width - 1] = getEntry(i + 1, getRhsOffset());
List<Integer> columnsToDrop = new ArrayList<Integer>();
columnsToDrop.add(0);
// positive cost non-artificial variables
for (int i = getNumObjectiveFunctions(); i < getArtificialVariableOffset(); i++) {
if (MathUtils.compareTo(tableau.getEntry(0, i), 0, epsilon) > 0) {
columnsToDrop.add(i);
}
}
// non-basic artificial variables
for (int i = 0; i < getNumArtificialVariables(); i++) {
int col = i + getArtificialVariableOffset();
if (getBasicRow(col) == null) {
columnsToDrop.add(col);
}
}
double[][] matrix = new double[getHeight() - 1][getWidth() - columnsToDrop.size()];
for (int i = 1; i < getHeight(); i++) {
int col = 0;
for (int j = 0; j < getWidth(); j++) {
if (!columnsToDrop.contains(j)) {
matrix[i - 1][col++] = tableau.getEntry(i, j);
}
}
}
this.tableau = new Array2DRowRealMatrix(matrix);
this.numArtificialVariables = 0;
}
@ -345,11 +333,11 @@ class SimplexTableau implements Serializable {
*/
protected RealPointValuePair getSolution() {
double[] coefficients = new double[getOriginalNumDecisionVariables()];
Integer negativeVarBasicRow = getBasicRowForSolution(getNegativeDecisionVariableOffset());
Integer negativeVarBasicRow = getBasicRow(getNegativeDecisionVariableOffset());
double mostNegative = negativeVarBasicRow == null ? 0 : getEntry(negativeVarBasicRow, getRhsOffset());
Set<Integer> basicRows = new HashSet<Integer>();
for (int i = 0; i < coefficients.length; i++) {
Integer basicRow = getBasicRowForSolution(getNumObjectiveFunctions() + i);
Integer basicRow = getBasicRow(getNumObjectiveFunctions() + i);
if (basicRows.contains(basicRow)) {
// if multiple variables can take a given value
// then we choose the first and set the rest equal to 0
@ -391,10 +379,8 @@ class SimplexTableau implements Serializable {
*/
protected void subtractRow(final int minuendRow, final int subtrahendRow,
final double multiple) {
for (int j = 0; j < getWidth(); j++) {
tableau.setEntry(minuendRow, j, tableau.getEntry(minuendRow, j) -
multiple * tableau.getEntry(subtrahendRow, j));
}
tableau.setRowVector(minuendRow, tableau.getRowVector(minuendRow)
.subtract(tableau.getRowVector(subtrahendRow).mapMultiply(multiple)));
}
/**

View File

@ -48,13 +48,17 @@ public class SimplexSolverTest {
@Test
public void testMath286() throws OptimizationException {
LinearObjectiveFunction f = new LinearObjectiveFunction(new double[] { 0.2, 0.3 }, 0 );
LinearObjectiveFunction f = new LinearObjectiveFunction(new double[] { 0.8, 0.2, 0.7, 0.3, 0.6, 0.4 }, 0 );
Collection<LinearConstraint> constraints = new ArrayList<LinearConstraint>();
constraints.add(new LinearConstraint(new double[] { 1, 1 }, Relationship.EQ, 23.0));
constraints.add(new LinearConstraint(new double[] { 1, 0, 1, 0, 1, 0 }, Relationship.EQ, 23.0));
constraints.add(new LinearConstraint(new double[] { 0, 1, 0, 1, 0, 1 }, Relationship.EQ, 23.0));
constraints.add(new LinearConstraint(new double[] { 1, 0, 0, 0, 0, 0 }, Relationship.GEQ, 10.0));
constraints.add(new LinearConstraint(new double[] { 0, 0, 1, 0, 0, 0 }, Relationship.GEQ, 8.0));
constraints.add(new LinearConstraint(new double[] { 0, 0, 0, 0, 1, 0 }, Relationship.GEQ, 5.0));
SimplexSolver solver = new SimplexSolver();
RealPointValuePair solution = solver.optimize(f, constraints, GoalType.MAXIMIZE, true);
Assert.assertEquals(6.9, solution.getValue(), .0000001);
Assert.assertEquals(25.8, solution.getValue(), .0000001);
}
@Test

View File

@ -50,12 +50,12 @@ public class SimplexTableauTest {
SimplexTableau tableau =
new SimplexTableau(f, constraints, GoalType.MAXIMIZE, false, 1.0e-6);
double[][] expectedTableau = {
{ 1, -15, -10, 25, 0, 0, 0},
{ 0, 1, 0, -1, 1, 0, 2},
{ 0, 0, 1, -1, 0, 1, 3},
{ 0, 1, 1, -2, 0, 0, 4}
{ 1, -15, -10, 0, 0, 0, 0},
{ 0, 1, 0, 1, 0, 0, 2},
{ 0, 0, 1, 0, 1, 0, 3},
{ 0, 1, 1, 0, 0, 1, 4}
};
tableau.discardArtificialVariables();
tableau.dropPhase1Objective();
assertMatrixEquals(expectedTableau, tableau.getData());
}