diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 66f0175a5..dc6539cd2 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -54,6 +54,11 @@ If the output is not quite correct, check for invisible trailing spaces! + + The "SimplexSolver" will now throw a "DimensionMismatchException" + when calling "optimize(...)" with linear constraints whose dimension + does not match the dimension of the objective function. + 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! Use Double.isNaN rather than x != x in FastMath. - - Fix potential branching errors in "FastMath#pow(double, double)" when - passing special values, i.e. infinity, due to erroneous JIT optimization. - Fixed equals/hashcode contract failure for Dfp. diff --git a/src/main/java/org/apache/commons/math4/optim/linear/SimplexSolver.java b/src/main/java/org/apache/commons/math4/optim/linear/SimplexSolver.java index d4b4259f7..743fe9bc3 100644 --- a/src/main/java/org/apache/commons/math4/optim/linear/SimplexSolver.java +++ b/src/main/java/org/apache/commons/math4/optim/linear/SimplexSolver.java @@ -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) diff --git a/src/main/java/org/apache/commons/math4/optim/linear/SimplexTableau.java b/src/main/java/org/apache/commons/math4/optim/linear/SimplexTableau.java index e869a7489..f0a842f16 100644 --- a/src/main/java/org/apache/commons/math4/optim/linear/SimplexTableau.java +++ b/src/main/java/org/apache/commons/math4/optim/linear/SimplexTableau.java @@ -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 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 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 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. */ diff --git a/src/test/java/org/apache/commons/math4/optim/linear/SimplexSolverTest.java b/src/test/java/org/apache/commons/math4/optim/linear/SimplexSolverTest.java index b112ac78e..987ab6c69 100644 --- a/src/test/java/org/apache/commons/math4/optim/linear/SimplexSolverTest.java +++ b/src/test/java/org/apache/commons/math4/optim/linear/SimplexSolverTest.java @@ -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 constraints = new ArrayList(); 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 constraints = new ArrayList(); 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 constraints = new ArrayList(); 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 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 constraints = new ArrayList(); @@ -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 constraints = new ArrayList(); + // 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; }