[MATH-1230] Throw a DimensionMismatchException if dimension of constraints and objective function does not match in SimplexSolver.

This commit is contained in:
Thomas Neidhart 2015-06-09 20:39:52 +02:00
parent 241dccdca6
commit 96eb80efe1
4 changed files with 78 additions and 25 deletions

View File

@ -54,6 +54,11 @@ If the output is not quite correct, check for invisible trailing spaces!
</release>
<release version="4.0" date="XXXX-XX-XX" description="">
<action dev="tn" type="fix" issue="MATH-1230">
The "SimplexSolver" will now throw a "DimensionMismatchException"
when calling "optimize(...)" with linear constraints whose dimension
does not match the dimension of the objective function.
</action>
<action dev="luc" type="add" >
Reimplemented pow(double, double) in FastMath, for better accuracy in
integral power cases and trying to fix erroneous JIT optimization again.
@ -67,10 +72,6 @@ If the output is not quite correct, check for invisible trailing spaces!
<action dev="luc" type="fix" issue="MATH-1222" due-to="Benedikt Ritter">
Use Double.isNaN rather than x != x in FastMath.
</action>
<action dev="tn" type="fix"> <!-- backported to 3.6 -->
Fix potential branching errors in "FastMath#pow(double, double)" when
passing special values, i.e. infinity, due to erroneous JIT optimization.
</action>
<action dev="luc" type="fix" issue="MATH-1118" > <!-- backported to 3.6 -->
Fixed equals/hashcode contract failure for Dfp.
</action>

View File

@ -19,6 +19,7 @@ package org.apache.commons.math4.optim.linear;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.math4.exception.DimensionMismatchException;
import org.apache.commons.math4.exception.TooManyIterationsException;
import org.apache.commons.math4.optim.OptimizationData;
import org.apache.commons.math4.optim.PointValuePair;
@ -146,6 +147,8 @@ public class SimplexSolver extends LinearOptimizer {
*
* @return {@inheritDoc}
* @throws TooManyIterationsException if the maximal number of iterations is exceeded.
* @throws DimensionMismatchException if the dimension of the constraints does not match the
* dimension of the objective function
*/
@Override
public PointValuePair optimize(OptimizationData... optData)

View File

@ -28,6 +28,7 @@ import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import org.apache.commons.math4.exception.DimensionMismatchException;
import org.apache.commons.math4.linear.Array2DRowRealMatrix;
import org.apache.commons.math4.linear.MatrixUtils;
import org.apache.commons.math4.linear.RealVector;
@ -112,6 +113,8 @@ class SimplexTableau implements Serializable {
* or {@link GoalType#MINIMIZE}.
* @param restrictToNonNegative Whether to restrict the variables to non-negative values.
* @param epsilon Amount of error to accept when checking for optimality.
* @throws DimensionMismatchException if the dimension of the constraints does not match the
* dimension of the objective function
*/
SimplexTableau(final LinearObjectiveFunction f,
final Collection<LinearConstraint> constraints,
@ -129,13 +132,16 @@ class SimplexTableau implements Serializable {
* @param restrictToNonNegative whether to restrict the variables to non-negative values
* @param epsilon amount of error to accept when checking for optimality
* @param maxUlps amount of error to accept in floating point comparisons
* @throws DimensionMismatchException if the dimension of the constraints does not match the
* dimension of the objective function
*/
SimplexTableau(final LinearObjectiveFunction f,
final Collection<LinearConstraint> constraints,
final GoalType goalType,
final boolean restrictToNonNegative,
final double epsilon,
final int maxUlps) {
final int maxUlps) throws DimensionMismatchException {
checkDimensions(f, constraints);
this.f = f;
this.constraints = normalizeConstraints(constraints);
this.restrictToNonNegative = restrictToNonNegative;
@ -153,6 +159,23 @@ class SimplexTableau implements Serializable {
initializeColumnLabels();
}
/**
* Checks that the dimensions of the objective function and the constraints match.
* @param f the objective function
* @param constraints the set of constraints
* @throws DimensionMismatchException if the constraint dimensions do not match with the
* dimension of the objective function
*/
private void checkDimensions(final LinearObjectiveFunction f,
final Collection<LinearConstraint> constraints) {
final int dimension = f.getCoefficients().getDimension();
for (final LinearConstraint constraint : constraints) {
final int constraintDimension = constraint.getCoefficients().getDimension();
if (constraintDimension != dimension) {
throw new DimensionMismatchException(constraintDimension, dimension);
}
}
}
/**
* Initialize the labels for the columns.
*/

View File

@ -20,6 +20,7 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.apache.commons.math4.exception.DimensionMismatchException;
import org.apache.commons.math4.exception.TooManyIterationsException;
import org.apache.commons.math4.optim.MaxIter;
import org.apache.commons.math4.optim.PointValuePair;
@ -52,13 +53,13 @@ public class SimplexSolverTest {
// x1,x2,x3,x4 >= 0
LinearObjectiveFunction f = new LinearObjectiveFunction(new double[] { 10, -57, -9, -24}, 0);
ArrayList<LinearConstraint> constraints = new ArrayList<LinearConstraint>();
constraints.add(new LinearConstraint(new double[] {0.5, -5.5, -2.5, 9}, Relationship.LEQ, 0));
constraints.add(new LinearConstraint(new double[] {0.5, -1.5, -0.5, 1}, Relationship.LEQ, 0));
constraints.add(new LinearConstraint(new double[] { 1, 0, 0, 0}, Relationship.LEQ, 1));
double epsilon = 1e-6;
SimplexSolver solver = new SimplexSolver();
PointValuePair solution = solver.optimize(f, new LinearConstraintSet(constraints),
@ -73,7 +74,7 @@ public class SimplexSolverTest {
public void testMath828() {
LinearObjectiveFunction f = new LinearObjectiveFunction(
new double[] { 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0}, 0.0);
ArrayList <LinearConstraint>constraints = new ArrayList<LinearConstraint>();
constraints.add(new LinearConstraint(new double[] {0.0, 39.0, 23.0, 96.0, 15.0, 48.0, 9.0, 21.0, 48.0, 36.0, 76.0, 19.0, 88.0, 17.0, 16.0, 36.0,}, Relationship.GEQ, 15.0));
@ -83,7 +84,7 @@ public class SimplexSolverTest {
constraints.add(new LinearConstraint(new double[] {25.0, -7.0, -99.0, -78.0, -25.0, -14.0, -16.0, -89.0, -39.0, -56.0, -53.0, -9.0, -18.0, -26.0, -11.0, -61.0,}, Relationship.GEQ, 0.0));
constraints.add(new LinearConstraint(new double[] {33.0, -95.0, -15.0, -4.0, -33.0, -3.0, -20.0, -96.0, -27.0, -13.0, -80.0, -24.0, -3.0, -13.0, -57.0, -76.0,}, Relationship.GEQ, 0.0));
constraints.add(new LinearConstraint(new double[] {7.0, -95.0, -39.0, -93.0, -7.0, -94.0, -94.0, -62.0, -76.0, -26.0, -53.0, -57.0, -31.0, -76.0, -53.0, -52.0,}, Relationship.GEQ, 0.0));
double epsilon = 1e-6;
PointValuePair solution = new SimplexSolver().optimize(DEFAULT_MAX_ITER, f, new LinearConstraintSet(constraints),
GoalType.MINIMIZE, new NonNegativeConstraint(true));
@ -95,7 +96,7 @@ public class SimplexSolverTest {
public void testMath828Cycle() {
LinearObjectiveFunction f = new LinearObjectiveFunction(
new double[] { 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0}, 0.0);
ArrayList <LinearConstraint>constraints = new ArrayList<LinearConstraint>();
constraints.add(new LinearConstraint(new double[] {0.0, 16.0, 14.0, 69.0, 1.0, 85.0, 52.0, 43.0, 64.0, 97.0, 14.0, 74.0, 89.0, 28.0, 94.0, 58.0, 13.0, 22.0, 21.0, 17.0, 30.0, 25.0, 1.0, 59.0, 91.0, 78.0, 12.0, 74.0, 56.0, 3.0, 88.0,}, Relationship.GEQ, 91.0));
@ -105,14 +106,14 @@ public class SimplexSolverTest {
constraints.add(new LinearConstraint(new double[] {41.0, -96.0, -41.0, -48.0, -70.0, -43.0, -43.0, -43.0, -97.0, -37.0, -85.0, -70.0, -45.0, -67.0, -87.0, -69.0, -94.0, -54.0, -54.0, -92.0, -79.0, -10.0, -35.0, -20.0, -41.0, -41.0, -65.0, -25.0, -12.0, -8.0, -46.0,}, Relationship.GEQ, 0.0));
constraints.add(new LinearConstraint(new double[] {27.0, -42.0, -65.0, -49.0, -53.0, -42.0, -17.0, -2.0, -61.0, -31.0, -76.0, -47.0, -8.0, -93.0, -86.0, -62.0, -65.0, -63.0, -22.0, -43.0, -27.0, -23.0, -32.0, -74.0, -27.0, -63.0, -47.0, -78.0, -29.0, -95.0, -73.0,}, Relationship.GEQ, 0.0));
constraints.add(new LinearConstraint(new double[] {15.0, -46.0, -41.0, -83.0, -98.0, -99.0, -21.0, -35.0, -7.0, -14.0, -80.0, -63.0, -18.0, -42.0, -5.0, -34.0, -56.0, -70.0, -16.0, -18.0, -74.0, -61.0, -47.0, -41.0, -15.0, -79.0, -18.0, -47.0, -88.0, -68.0, -55.0,}, Relationship.GEQ, 0.0));
double epsilon = 1e-6;
PointValuePair solution = new SimplexSolver().optimize(DEFAULT_MAX_ITER, f,
new LinearConstraintSet(constraints),
GoalType.MINIMIZE, new NonNegativeConstraint(true),
PivotSelectionRule.BLAND);
Assert.assertEquals(1.0d, solution.getValue(), epsilon);
Assert.assertTrue(validSolution(solution, constraints, epsilon));
Assert.assertTrue(validSolution(solution, constraints, epsilon));
}
@Test
@ -195,7 +196,7 @@ public class SimplexSolverTest {
SimplexSolver solver = new SimplexSolver();
PointValuePair solution = solver.optimize(DEFAULT_MAX_ITER, f, new LinearConstraintSet(constraints),
GoalType.MINIMIZE, new NonNegativeConstraint(false));
Assert.assertTrue(Precision.compareTo(solution.getPoint()[0] * 200.d, 1.d, epsilon) >= 0);
Assert.assertEquals(0.0050, solution.getValue(), epsilon);
}
@ -219,13 +220,13 @@ public class SimplexSolverTest {
SimplexSolver simplex = new SimplexSolver();
PointValuePair solution = simplex.optimize(DEFAULT_MAX_ITER, f, new LinearConstraintSet(constraints),
GoalType.MINIMIZE, new NonNegativeConstraint(false));
Assert.assertTrue(Precision.compareTo(solution.getPoint()[0], -1e-18d, epsilon) >= 0);
Assert.assertEquals(1.0d, solution.getPoint()[1], epsilon);
Assert.assertEquals(1.0d, solution.getPoint()[1], epsilon);
Assert.assertEquals(0.0d, solution.getPoint()[2], epsilon);
Assert.assertEquals(1.0d, solution.getValue(), epsilon);
}
@Test
public void testMath272() {
LinearObjectiveFunction f = new LinearObjectiveFunction(new double[] { 2, 2, 1 }, 0);
@ -361,12 +362,12 @@ public class SimplexSolverTest {
@Test
public void testMath930() {
Collection<LinearConstraint> constraints = createMath930Constraints();
double[] objFunctionCoeff = new double[33];
objFunctionCoeff[3] = 1;
LinearObjectiveFunction f = new LinearObjectiveFunction(objFunctionCoeff, 0);
SimplexSolver solver = new SimplexSolver(1e-4, 10, 1e-6);
PointValuePair solution = solver.optimize(new MaxIter(1000), f, new LinearConstraintSet(constraints),
GoalType.MINIMIZE, new NonNegativeConstraint(true));
Assert.assertEquals(0.3752298, solution.getValue(), 1e-4);
@ -761,7 +762,7 @@ public class SimplexSolverTest {
public void testSolutionCallback() {
// re-use the problem from testcase for MATH-288
// it normally requires 5 iterations
LinearObjectiveFunction f = new LinearObjectiveFunction(new double[] { 7, 3, 0, 0 }, 0 );
List<LinearConstraint> constraints = new ArrayList<LinearConstraint>();
@ -773,7 +774,7 @@ public class SimplexSolverTest {
final SimplexSolver solver = new SimplexSolver();
final SolutionCallback callback = new SolutionCallback();
Assert.assertNull(callback.getSolution());
Assert.assertFalse(callback.isSolutionOptimal());
@ -784,7 +785,7 @@ public class SimplexSolverTest {
} catch (TooManyIterationsException ex) {
// expected
}
final PointValuePair solution = callback.getSolution();
Assert.assertNotNull(solution);
Assert.assertTrue(validSolution(solution, constraints, 1e-4));
@ -793,6 +794,31 @@ public class SimplexSolverTest {
Assert.assertEquals(7.0, solution.getValue(), 1e-4);
}
@Test(expected=DimensionMismatchException.class)
public void testDimensionMatch() {
// min 2x1 +15x2 +18x3
// Subject to
// -x1 +2x2 -6x3 <=-10
// x2 +2x3 <= 6
// 2x1 +10x3 <= 19
// -x1 +x2 <= -2
// x1,x2,x3 >= 0
LinearObjectiveFunction f = new LinearObjectiveFunction(new double[] { 2, 15, 18 }, 0);
Collection<LinearConstraint> constraints = new ArrayList<LinearConstraint>();
// this constraint is wrong, the dimension is less than expected one
constraints.add(new LinearConstraint(new double[] { -1, 2 - 6 }, Relationship.LEQ, -10));
constraints.add(new LinearConstraint(new double[] { 0, 1, 2 }, Relationship.LEQ, 6));
constraints.add(new LinearConstraint(new double[] { 2, 0, 10 }, Relationship.LEQ, 19));
constraints.add(new LinearConstraint(new double[] { -1, 1, 0 }, Relationship.LEQ, -2));
SimplexSolver solver = new SimplexSolver();
solver.optimize(f,
new LinearConstraintSet(constraints),
new NonNegativeConstraint(true),
PivotSelectionRule.BLAND);
}
/**
* Converts a test string to a {@link LinearConstraint}.
* Ex: x0 + x1 + x2 + x3 - x12 = 0
@ -831,20 +857,20 @@ public class SimplexSolverTest {
for (int i = 0; i < vals.length; i++) {
result += vals[i] * coeffs[i];
}
switch (c.getRelationship()) {
case EQ:
if (!Precision.equals(result, c.getValue(), epsilon)) {
return false;
}
break;
case GEQ:
if (Precision.compareTo(result, c.getValue(), epsilon) < 0) {
return false;
}
break;
case LEQ:
if (Precision.compareTo(result, c.getValue(), epsilon) > 0) {
return false;
@ -852,7 +878,7 @@ public class SimplexSolverTest {
break;
}
}
return true;
}