From aa91ffbe330de2586010ab91dc3bb18eef33974b Mon Sep 17 00:00:00 2001 From: Luc Maisonobe Date: Wed, 9 Sep 2009 08:48:03 +0000 Subject: [PATCH] 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 --- .../optimization/linear/SimplexSolver.java | 2 +- .../optimization/linear/SimplexTableau.java | 128 ++++++++---------- .../linear/SimplexSolverTest.java | 10 +- .../linear/SimplexTableauTest.java | 10 +- 4 files changed, 70 insertions(+), 80 deletions(-) diff --git a/src/main/java/org/apache/commons/math/optimization/linear/SimplexSolver.java b/src/main/java/org/apache/commons/math/optimization/linear/SimplexSolver.java index b7c01e921..5c25548dd 100644 --- a/src/main/java/org/apache/commons/math/optimization/linear/SimplexSolver.java +++ b/src/main/java/org/apache/commons/math/optimization/linear/SimplexSolver.java @@ -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); diff --git a/src/main/java/org/apache/commons/math/optimization/linear/SimplexTableau.java b/src/main/java/org/apache/commons/math/optimization/linear/SimplexTableau.java index 5f419c129..1cb45cce7 100644 --- a/src/main/java/org/apache/commons/math/optimization/linear/SimplexTableau.java +++ b/src/main/java/org/apache/commons/math/optimization/linear/SimplexTableau.java @@ -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 columnsToDrop = new ArrayList(); + 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 basicRows = new HashSet(); 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))); } /** diff --git a/src/test/java/org/apache/commons/math/optimization/linear/SimplexSolverTest.java b/src/test/java/org/apache/commons/math/optimization/linear/SimplexSolverTest.java index 3e0ce2bfe..a2502d6c2 100644 --- a/src/test/java/org/apache/commons/math/optimization/linear/SimplexSolverTest.java +++ b/src/test/java/org/apache/commons/math/optimization/linear/SimplexSolverTest.java @@ -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 constraints = new ArrayList(); - 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 diff --git a/src/test/java/org/apache/commons/math/optimization/linear/SimplexTableauTest.java b/src/test/java/org/apache/commons/math/optimization/linear/SimplexTableauTest.java index 0c8edd283..c09f44c67 100644 --- a/src/test/java/org/apache/commons/math/optimization/linear/SimplexTableauTest.java +++ b/src/test/java/org/apache/commons/math/optimization/linear/SimplexTableauTest.java @@ -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()); }