You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by er...@apache.org on 2021/08/09 16:41:41 UTC

[commons-math] 09/09: MATH-1623: Add parameterized unit tests for simplex-based optimizers.

This is an automated email from the ASF dual-hosted git repository.

erans pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-math.git

commit fdbb8b98f5c0dba55d1bc607434ea00e2b9c3145
Author: Gilles Sadowski <gi...@gmail.com>
AuthorDate: Mon Aug 9 18:27:00 2021 +0200

    MATH-1623: Add parameterized unit tests for simplex-based optimizers.
---
 .../scalar/noderiv/SimplexOptimizerTest.java       | 220 +++++++++++++++++++++
 .../std_test_func.simplex.multidirectional.csv     |  44 +++++
 .../noderiv/std_test_func.simplex.nelder_mead.csv  |  44 +++++
 3 files changed, 308 insertions(+)

diff --git a/commons-math-legacy/src/test/java/org/apache/commons/math4/legacy/optim/nonlinear/scalar/noderiv/SimplexOptimizerTest.java b/commons-math-legacy/src/test/java/org/apache/commons/math4/legacy/optim/nonlinear/scalar/noderiv/SimplexOptimizerTest.java
new file mode 100644
index 0000000..bcf09e7
--- /dev/null
+++ b/commons-math-legacy/src/test/java/org/apache/commons/math4/legacy/optim/nonlinear/scalar/noderiv/SimplexOptimizerTest.java
@@ -0,0 +1,220 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.math4.legacy.optim.nonlinear.scalar.noderiv;
+
+import java.util.Arrays;
+import org.opentest4j.AssertionFailedError;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.extension.ParameterContext;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.aggregator.ArgumentsAggregator;
+import org.junit.jupiter.params.aggregator.ArgumentsAccessor;
+import org.junit.jupiter.params.aggregator.ArgumentsAggregationException;
+import org.junit.jupiter.params.aggregator.AggregateWith;
+import org.junit.jupiter.params.provider.CsvFileSource;
+import org.apache.commons.math4.legacy.core.MathArrays;
+import org.apache.commons.math4.legacy.analysis.MultivariateFunction;
+import org.apache.commons.math4.legacy.optim.InitialGuess;
+import org.apache.commons.math4.legacy.optim.MaxEval;
+import org.apache.commons.math4.legacy.optim.PointValuePair;
+import org.apache.commons.math4.legacy.optim.nonlinear.scalar.GoalType;
+import org.apache.commons.math4.legacy.optim.nonlinear.scalar.ObjectiveFunction;
+import org.apache.commons.math4.legacy.optim.nonlinear.scalar.TestFunction;
+
+/**
+ * Tests for {@link SimplexOptimizer simplex-based algorithms}.
+ */
+public class SimplexOptimizerTest {
+    private static final String NELDER_MEAD_INPUT_FILE = "std_test_func.simplex.nelder_mead.csv";
+    private static final String MULTIDIRECTIONAL_INPUT_FILE = "std_test_func.simplex.multidirectional.csv";
+
+    @ParameterizedTest
+    @CsvFileSource(resources = NELDER_MEAD_INPUT_FILE)
+    void testFunctionWithNelderMead(@AggregateWith(TaskAggregator.class) Task task) {
+        task.run(new NelderMeadTransform());
+    }
+
+    @ParameterizedTest
+    @CsvFileSource(resources = MULTIDIRECTIONAL_INPUT_FILE)
+    void testFunctionWithMultiDirectional(@AggregateWith(TaskAggregator.class) Task task) {
+        task.run(new MultiDirectionalTransform());
+    }
+
+    /**
+     * Optimization task.
+     */
+    public static class Task {
+        /** Function evaluations hard count (debugging). */
+        private static final int FUNC_EVAL_DEBUG = 20000;
+        /** Default convergence criterion. */
+        private static final double CONVERGENCE_CHECK = 1e-9;
+        /** Default simplex size. */
+        private static final double SIDE_LENGTH = 1;
+        /** Function. */
+        private final MultivariateFunction f;
+        /** Initial value. */
+        private final double[] start;
+        /** Optimum. */
+        private final double[] optimum;
+        /** Tolerance. */
+        private final double pointTolerance;
+        /** Allowed function evaluations. */
+        private final int functionEvaluations;
+        /** Repeats on failure. */
+        private final int repeatsOnFailure;
+        /** Range of random noise. */
+        private double jitter;
+
+        /**
+         * @param f Test function.
+         * @param start Start point.
+         * @param optimum Optimum.
+         * @param pointTolerance Allowed distance between result and
+         * {@code optimum}.
+         * @param functionEvaluations Allowed number of function evaluations.
+         * @param repeatsOnFailure Maximum number of times to rerun when an
+         * {@link AssertionFailedError} is thrown.
+         * @param jitter Size of random jitter.
+         */
+        Task(MultivariateFunction f,
+             double[] start,
+             double[] optimum,
+             double pointTolerance,
+             int functionEvaluations,
+             int repeatsOnFailure,
+             double jitter) {
+            this.f = f;
+            this.start = start;
+            this.optimum = optimum;
+            this.pointTolerance = pointTolerance;
+            this.functionEvaluations = functionEvaluations;
+            this.repeatsOnFailure = repeatsOnFailure;
+            this.jitter = jitter;
+        }
+
+        @Override
+        public String toString() {
+            return f.toString();
+        }
+
+        /**
+         * @param factory Simplex transform factory.
+         */
+        public void run(Simplex.TransformFactory factory) {
+            // Let run with a maximum number of evaluations larger than expected
+            // (as specified by "functionEvaluations") in order to have the unit
+            // test failure message (see assertion below) report the actual number
+            // required by the current code.
+            final int maxEval = Math.max(functionEvaluations, FUNC_EVAL_DEBUG);
+
+            int currentRetry = -1;
+            AssertionFailedError lastFailure = null;
+            while (currentRetry++ <= repeatsOnFailure) {
+                try {
+                    final String name = f.toString();
+
+                    final SimplexOptimizer optim = new SimplexOptimizer(-1, CONVERGENCE_CHECK);
+                    final Simplex initialSimplex =
+                        Simplex.alongAxes(OptimTestUtils.point(start.length,
+                                                               SIDE_LENGTH,
+                                                               jitter));
+                    final double[] startPoint = OptimTestUtils.point(start, jitter);
+                    final PointValuePair result =
+                        optim.optimize(new MaxEval(maxEval),
+                                       new ObjectiveFunction(f),
+                                       GoalType.MINIMIZE,
+                                       new InitialGuess(startPoint),
+                                       initialSimplex,
+                                       factory);
+
+                    final double[] endPoint = result.getPoint();
+                    final double funcValue = result.getValue();
+                    final double dist = MathArrays.distance(optimum, endPoint);
+                    Assertions.assertEquals(0d, dist, pointTolerance,
+                                            name + ": distance to optimum" +
+                                            " f(" + Arrays.toString(endPoint) + ")=" +
+                                            funcValue);
+
+                    final int nEval = optim.getEvaluations();
+                    Assertions.assertTrue(nEval < functionEvaluations,
+                                          name + ": nEval=" + nEval);
+
+                    break; // Assertions passed: Retry not neccessary.
+                } catch (AssertionFailedError e) {
+                    if (currentRetry >= repeatsOnFailure) {
+                        // Allowed repeats have been exhausted: Bail out.
+                        throw e;
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Helper for preparing a {@link Task}.
+     */
+    public static class TaskAggregator implements ArgumentsAggregator {
+        @Override
+        public Object aggregateArguments(ArgumentsAccessor a,
+                                         ParameterContext context)
+            throws ArgumentsAggregationException {
+
+            int index = 0; // Argument index.
+
+            final TestFunction funcGen = a.get(index++, TestFunction.class);
+            final int dim = a.getInteger(index++);
+            final double[] start = toArrayOfDoubles(a.getString(index++), dim);
+            final double[] optimum = toArrayOfDoubles(a.getString(index++), dim);
+            final double pointTol = a.getDouble(index++);
+            final int funcEval = a.getInteger(index++);
+            final int repeat = a.getInteger(index++);
+            final double jitter = a.getDouble(index++);
+
+            return new Task(funcGen.withDimension(dim),
+                            start,
+                            optimum,
+                            pointTol,
+                            funcEval,
+                            repeat,
+                            jitter);
+        }
+
+        /**
+         * @param params Comma-separated list of values.
+         * @param dim Expected number of values.
+         * @return an array of {@code double} values.
+         * @throws ArgumentsAggregationException if the number of values
+         * is not equal to {@code dim}.
+         */
+        private static double[] toArrayOfDoubles(String params,
+                                                 int dim) {
+            final String[] s = params.trim().split("\\s+");
+
+            if (s.length != dim) {
+                final String msg = "Expected " + dim + " values: " + Arrays.toString(s);
+                throw new ArgumentsAggregationException(msg);
+            }
+
+            final double[] p = new double[dim];
+            for (int i = 0; i < dim; i++) {
+                p[i] = Double.valueOf(s[i]);
+            }
+
+            return p;
+        }
+    }
+}
diff --git a/commons-math-legacy/src/test/resources/org/apache/commons/math4/legacy/optim/nonlinear/scalar/noderiv/std_test_func.simplex.multidirectional.csv b/commons-math-legacy/src/test/resources/org/apache/commons/math4/legacy/optim/nonlinear/scalar/noderiv/std_test_func.simplex.multidirectional.csv
new file mode 100644
index 0000000..3bc6750
--- /dev/null
+++ b/commons-math-legacy/src/test/resources/org/apache/commons/math4/legacy/optim/nonlinear/scalar/noderiv/std_test_func.simplex.multidirectional.csv
@@ -0,0 +1,44 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+#
+# CSV Format defined in "SimplexOptimizerTest.Task" class.
+# Columns are:
+#  0: function name (value from "TestFunction" enum)
+#  1: space dimension (n)
+#  2: nominal start point ("n" space-separated values)
+#  3: optimum ("n" space-separated values)
+#  4: maximum expected distance from the result to the optimum
+#  5: expected number of function evaluations
+#  6: number of retries in case of assertion failure
+#  7: size of the random noise (to generate slightly different initial conditions)
+#
+# Caveat: Some tests are commented out (cf. JIRA: MATH-1552).
+#
+PARABOLA, 4, 2.5 3.1 4.6 5.8, 0 0 0 0, 1e-4, 340, 3, 2e-1
+ROSENBROCK, 2, -1.2 1, 1 1, 2e-3, 9200, 3, 1e-1
+ROSENBROCK, 10, -0.1 0.1 0.2 -0.1 -0.2 0.3 0.2 -0.1 0.2 -0.3, 1 1 1 1 1 1 1 1 1 1, 3e-3, 100000, 3, 5e-2
+#POWELL, 4, 3 -1 -2 1, 0 0 0 0, 5e-3, 420, 3, 1e-1
+CIGAR, 13, -1.2 2.3 -3.2 2.1 1.2 -2.3 3.2 -2.1 -1.2 2.3 -3.2 2.1 -1.2, 0 0 0 0 0 0 0 0 0 0 0 0 0, 1e-6, 7000, 3, 1e-1
+SPHERE, 13, -1.2 2.3 -3.2 2.1 1.2 -2.3 3.2 -2.1 -1.2 2.3 -3.2 2.1 -1.2, 0 0 0 0 0 0 0 0 0 0 0 0 0, 5e-5, 3600, 3, 1e-1
+ELLI, 10, 2 3 4 -3 -2 -1 2 3 4 3, 0 0 0 0 0 0 0 0 0 0, 1e-4, 50000, 3, 1e-1
+TWO_AXES, 10, 2 3 4 -3 -2 -1 2 3 4 3, 0 0 0 0 0 0 0 0 0 0, 1e-6, 3200, 3, 1e-1
+CIG_TAB, 10, 2 3 4 -3 -2 -1 2 3 4 3, 0 0 0 0 0 0 0 0 0 0, 5e-6, 2700, 3, 1e-1
+TABLET, 11, 2 3 4 -3 -2 -1 2 3 4 3 -1, 0 0 0 0 0 0 0 0 0 0 0, 5e-6, 3000, 3, 1e-1
+DIFF_POW, 7, 1 -1 1 -1 1 -1 1, 0 0 0 0 0 0 0, 5e-4, 2500, 3, 1e-1
+SS_DIFF_POW, 6, -3.2 2.1 1.2 -2.3 3.2 -2.1, 0 0 0 0 0 0, 1e-3, 4000, 3, 1e-1
+ACKLEY, 4, 3 4 -3 -2, 0 0 0 0, 1e-6, 700, 3, 5e-1
+#RASTRIGIN, 4, 3 4 -3 -2, 0 0 0 0, 1e-6, 10000, 3, 5e-1
diff --git a/commons-math-legacy/src/test/resources/org/apache/commons/math4/legacy/optim/nonlinear/scalar/noderiv/std_test_func.simplex.nelder_mead.csv b/commons-math-legacy/src/test/resources/org/apache/commons/math4/legacy/optim/nonlinear/scalar/noderiv/std_test_func.simplex.nelder_mead.csv
new file mode 100644
index 0000000..532b769
--- /dev/null
+++ b/commons-math-legacy/src/test/resources/org/apache/commons/math4/legacy/optim/nonlinear/scalar/noderiv/std_test_func.simplex.nelder_mead.csv
@@ -0,0 +1,44 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+#
+# CSV Format defined in "SimplexOptimizerTest.Task" class.
+# Columns are:
+#  0: function name (value from "TestFunction" enum)
+#  1: space dimension (n)
+#  2: nominal start point ("n" space-separated values)
+#  3: optimum ("n" space-separated values)
+#  4: maximum expected distance from the result to the optimum
+#  5: expected number of function evaluations
+#  6: number of retries in case of assertion failure
+#  7: size of the random noise (to generate slightly different initial conditions)
+#
+# Caveat: Some tests are commented out (cf. JIRA: MATH-1552).
+#
+PARABOLA, 4, 2.5 3.1 4.6 5.8, 0 0 0 0, 1e-4, 200, 3, 2e-1
+ROSENBROCK, 2, -1.2 1, 1 1, 1e-4, 180, 3, 1e-1
+ROSENBROCK, 10, -0.1 0.1 0.2 -0.1 -0.2 0.3 0.2 -0.1 0.2 -0.3, 1 1 1 1 1 1 1 1 1 1, 5e-5, 9000, 3, 5e-2
+POWELL, 4, 3 -1 -2 1, 0 0 0 0, 5e-3, 420, 3, 1e-1
+CIGAR, 13, -1.2 2.3 -3.2 2.1 1.2 -2.3 3.2 -2.1 -1.2 2.3 -3.2 2.1 -1.2, 0 0 0 0 0 0 0 0 0 0 0 0 0, 5e-5, 7000, 3, 1e-1
+SPHERE, 13, -1.2 2.3 -3.2 2.1 1.2 -2.3 3.2 -2.1 -1.2 2.3 -3.2 2.1 -1.2, 0 0 0 0 0 0 0 0 0 0 0 0 0, 5e-4, 3000, 3, 1e-1
+ELLI, 10, 2 3 4 -3 -2 -1 2 3 4 3, 0 0 0 0 0 0 0 0 0 0, 1e-4, 50000, 3, 1e-1
+#TWO_AXES, 10, 2 3 4 -3 -2 -1 2 3 4 3, 0 0 0 0 0 0 0 0 0 0, 1e-4, 5000, 3, 1e-1
+#CIG_TAB, 10, 2 3 4 -3 -2 -1 2 3 4 3, 0 0 0 0 0 0 0 0 0 0, 1e-4, 7000, 3, 1e-1
+TABLET, 11, 2 3 4 -3 -2 -1 2 3 4 3 -1, 0 0 0 0 0 0 0 0 0 0 0, 2e-4, 3100, 3, 1e-1
+#DIFF_POW, 7, 1 -1 1 -1 1 -1 1, 0 0 0 0 0 0 0, 5e-4, 2500, 3, 1e-1
+SS_DIFF_POW, 6, -3.2 2.1 1.2 -2.3 3.2 -2.1, 0 0 0 0 0 0, 1e-3, 4000, 3, 1e-1
+ACKLEY, 4, 3 4 -3 -2, 0 0 0 0, 1e-6, 350, 3, 5e-1
+#RASTRIGIN, 4, 3 4 -3 -2, 0 0 0 0, 1e-6, 10000, 3, 5e-1