Simplex.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *      http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */

  17. package org.apache.commons.math4.legacy.optim.nonlinear.scalar.noderiv;

  18. import java.util.List;
  19. import java.util.ArrayList;
  20. import java.util.Arrays;
  21. import java.util.Comparator;
  22. import java.util.Collections;
  23. import java.util.function.UnaryOperator;
  24. import java.util.function.DoublePredicate;

  25. import org.apache.commons.math4.legacy.analysis.MultivariateFunction;
  26. import org.apache.commons.math4.legacy.exception.DimensionMismatchException;
  27. import org.apache.commons.math4.legacy.exception.MathIllegalArgumentException;
  28. import org.apache.commons.math4.legacy.exception.NotStrictlyPositiveException;
  29. import org.apache.commons.math4.legacy.exception.ZeroException;
  30. import org.apache.commons.math4.legacy.exception.util.LocalizedFormats;
  31. import org.apache.commons.math4.legacy.optim.OptimizationData;
  32. import org.apache.commons.math4.legacy.optim.PointValuePair;

  33. /**
  34.  * Represents a <a href="https://en.wikipedia.org/wiki/Simplex">simplex</a>.
  35.  *
  36.  * @see SimplexOptimizer
  37.  */
  38. public final class Simplex implements OptimizationData {
  39.     /** Coordinates. */
  40.     private final List<PointValuePair> points;

  41.     /**
  42.      * Builds from a given set of coordinates.
  43.      *
  44.      * @param referenceSimplex Reference simplex.
  45.      * @throws NotStrictlyPositiveException if the reference simplex does not
  46.      * contain at least one point.
  47.      * @throws DimensionMismatchException if there is a dimension mismatch
  48.      * in the reference simplex.
  49.      * @throws IllegalArgumentException if one of its vertices is duplicated.
  50.      */
  51.     private Simplex(double[][] referenceSimplex) {
  52.         if (referenceSimplex.length <= 0) {
  53.             throw new NotStrictlyPositiveException(LocalizedFormats.SIMPLEX_NEED_ONE_POINT,
  54.                                                    referenceSimplex.length);
  55.         }
  56.         final int len = referenceSimplex.length;
  57.         points = new ArrayList<>(len);

  58.         final int dim = len - 1;

  59.         // Loop over vertices.
  60.         for (int i = 0; i < len; i++) {
  61.             final double[] refI = referenceSimplex[i];

  62.             // Safety checks.
  63.             if (refI.length != dim) {
  64.                 throw new DimensionMismatchException(refI.length, dim);
  65.             }
  66.             for (int j = 1; j < i; j++) {
  67.                 final double[] refJ = referenceSimplex[j];
  68.                 boolean allEquals = true;
  69.                 for (int k = 0; k < dim; k++) {
  70.                     if (refI[k] != refJ[k]) {
  71.                         allEquals = false;
  72.                         break;
  73.                     }
  74.                 }
  75.                 if (allEquals) {
  76.                     throw new MathIllegalArgumentException(LocalizedFormats.EQUAL_VERTICES_IN_SIMPLEX,
  77.                                                            i, j);
  78.                 }
  79.             }

  80.             points.add(new PointValuePair(refI, Double.NaN));
  81.         }
  82.     }

  83.     /**
  84.      * Builds from an existing simplex.
  85.      *
  86.      * @param points Simplex data.  Reference will be stored in the newly
  87.      * constructed instance.
  88.      */
  89.     private Simplex(List<PointValuePair> points) {
  90.         this.points = points;
  91.     }

  92.     /**
  93.      * Builds from a given set of coordinates.
  94.      *
  95.      * @param simplex Simplex coordinates.
  96.      * @return a new instance.
  97.      * @throws NotStrictlyPositiveException if the reference simplex does not
  98.      * contain at least one point.
  99.      * @throws DimensionMismatchException if there is a dimension mismatch
  100.      * in the reference simplex.
  101.      * @throws IllegalArgumentException if one of its vertices is duplicated.
  102.      */
  103.     public static Simplex of(double[][] simplex) {
  104.         return new Simplex(simplex);
  105.     }

  106.     /**
  107.      * Builds simplex with the given side length.
  108.      *
  109.      * @param dim Space dimensions.
  110.      * @param sideLength Length of the sides of the hypercube.
  111.      * @return a new instance.
  112.      */
  113.     public static Simplex equalSidesAlongAxes(int dim,
  114.                                               double sideLength) {
  115.         final double[] steps = new double[dim];
  116.         Arrays.fill(steps, sideLength);
  117.         return alongAxes(steps);
  118.     }

  119.     /**
  120.      * The start configuration for simplex is built from a box parallel to
  121.      * the canonical axes of the space. The simplex is the subset of vertices
  122.      * of a box parallel to the canonical axes. It is built as the path followed
  123.      * while traveling from one vertex of the box to the diagonally opposite
  124.      * vertex moving only along the box edges. The first vertex of the box will
  125.      * be located at the origin of the coordinate system.
  126.      *
  127.      * To be used for simplex-based optimization, the simplex must be
  128.      * {@link #translate(double[]) translated} so that its first vertex will be
  129.      * the {@link org.apache.commons.math4.legacy.optim.InitialGuess initial guess}.
  130.      *
  131.      * For example, in dimension 3 a simplex has 4 vertices. Setting the
  132.      * steps to (1, 10, 2) and the start point to (1, 1, 1) would imply the
  133.      * initial simplex would be:
  134.      * <ol>
  135.      *  <li>(1, 1, 1),</li>
  136.      *  <li>(2, 1, 1),</li>
  137.      *  <li>(2, 11, 1),</li>
  138.      *  <li>(2, 11, 3).</li>
  139.      * </ol>
  140.      *
  141.      * @param steps Steps along the canonical axes representing box edges.
  142.      * They may be negative but not zero.
  143.      * @throws ZeroException if one of the steps is zero.
  144.      * @return a new instance.
  145.      */
  146.     public static Simplex alongAxes(double[] steps) {
  147.         if (steps.length == 0) {
  148.             throw new ZeroException();
  149.         }
  150.         final int dim = steps.length;
  151.         final int len = dim + 1;

  152.         // Only the relative position of the n final vertices with respect
  153.         // to the first one are stored.
  154.         final double[][] simplex = new double[len][dim];
  155.         for (int i = 1; i < len; i++) { // First point is the origin (zero).
  156.             final double[] vertexI = simplex[i];
  157.             for (int j = 0; j < i; j++) {
  158.                 if (steps[j] == 0) {
  159.                     throw new ZeroException();
  160.                 }
  161.                 System.arraycopy(steps, 0, vertexI, 0, j + 1);
  162.             }
  163.         }

  164.         return new Simplex(simplex);
  165.     }

  166.     /**
  167.      * Returns the space dimension.
  168.      *
  169.      * @return the dimension of the simplex.
  170.      */
  171.     public int getDimension() {
  172.         return points.size() - 1;
  173.     }

  174.     /**
  175.      * Returns the number of vertices.
  176.      *
  177.      * @return the size of the simplex.
  178.      */
  179.     public int getSize() {
  180.         return points.size();
  181.     }

  182.     /**
  183.      * Evaluates the (non-evaluated) simplex points and returns a new instance
  184.      * with vertices sorted from best to worst.
  185.      *
  186.      * @param function Evaluation function.
  187.      * @param comparator Comparator for sorting vertices, from best to worst.
  188.      * @return a new instance in which the vertices are sorted according to
  189.      * the given {@code comparator}.
  190.      */
  191.     public Simplex evaluate(MultivariateFunction function,
  192.                             Comparator<PointValuePair> comparator) {
  193.         final List<PointValuePair> newPoints = new ArrayList<>(points.size());
  194.         for (PointValuePair pv : points) {
  195.             final double[] coord = pv.getPoint();
  196.             final double value = Double.isNaN(pv.getValue()) ?
  197.                 function.value(coord) :
  198.                 pv.getValue();

  199.             newPoints.add(new PointValuePair(coord, value, false));
  200.         }

  201.         Collections.sort(newPoints, comparator);
  202.         return new Simplex(newPoints);
  203.     }

  204.     /**
  205.      * Retrieves a copy of the simplex point stored at {@code index}.
  206.      *
  207.      * @param index Location.
  208.      * @return the point at location {@code index}.
  209.      */
  210.     public PointValuePair get(int index) {
  211.         final PointValuePair p = points.get(index);
  212.         return new PointValuePair(p.getPoint(), p.getValue());
  213.     }

  214.     /**
  215.      * Creates a (deep) copy of the simplex points.
  216.      *
  217.      * @return the points.
  218.      */
  219.     public List<PointValuePair> asList() {
  220.         return asList(0, points.size());
  221.     }

  222.     /**
  223.      * Generator of simplex transform.
  224.      *
  225.      * @see MultiDirectionalTransform
  226.      * @see NelderMeadTransform
  227.      * @see HedarFukushimaTransform
  228.      */
  229.     public interface TransformFactory extends OptimizationData {
  230.         /**
  231.          * Creates a simplex transformation.
  232.          *
  233.          * @param evaluationFunction Evaluation function.
  234.          * @param comparator Vertex fitness comparator.
  235.          * @param saAcceptance Simulated annealing acceptance test.
  236.          * @return the simplex transform operator.
  237.          */
  238.         UnaryOperator<Simplex> create(MultivariateFunction evaluationFunction,
  239.                                       Comparator<PointValuePair> comparator,
  240.                                       DoublePredicate saAcceptance);
  241.     }

  242.     /**
  243.      * Creates a (deep) copy of the simplex points within slots
  244.      * {@code from} (included) and {@code to} (excluded).
  245.      *
  246.      * @param from Index of the first point to retrieve.
  247.      * @param to One past the index of the last point to retrieve.
  248.      * @return the points.
  249.      * @throws IllegalArgumentException if {@code from} and {@code to} are
  250.      * not within the {@code [0, n + 1]} interval (where {@code n} is the
  251.      * space dimension) or {@code from > to}.
  252.      */
  253.     /* package private */ List<PointValuePair> asList(int from,
  254.                                                       int to) {
  255.         if (from < 0 ||
  256.             to > points.size() ||
  257.             from > to) {
  258.             throw new IllegalArgumentException("Index");
  259.         }

  260.         final int len = to - from;
  261.         final List<PointValuePair> copy = new ArrayList<>(len);
  262.         for (int i = from; i < to; i++) {
  263.             copy.add(get(i));
  264.         }

  265.         return copy;
  266.     }

  267.     /**
  268.      * Utility for evaluating a point with coordinates \( a_i + s (b_i - a_i) \).
  269.      *
  270.      * @param a Cartesian coordinates.
  271.      * @param s Scaling factor.
  272.      * @param b Cartesian coordinates.
  273.      * @param function Evaluation function.
  274.      * @return a new point.
  275.      */
  276.     /* package private */ static PointValuePair newPoint(double[] a,
  277.                                                          double s,
  278.                                                          double[] b,
  279.                                                          MultivariateFunction function) {
  280.         final int dim = a.length;
  281.         final double[] r = new double[dim];
  282.         for (int i = 0; i < dim; i++) {
  283.             final double m = a[i];
  284.             r[i] = m + s * (b[i] - m);
  285.         }

  286.         return new PointValuePair(r, function.value(r), false);
  287.     }

  288.     /**
  289.      * Utility for the "shrinking" a simplex: All the points will be
  290.      * transformed except the one at index 0.
  291.      *
  292.      * @param sigma Shrink factor.
  293.      * @param function Evaluation function.
  294.      * @return a new instance.
  295.      */
  296.     /* package private */ Simplex shrink(double sigma,
  297.                                          MultivariateFunction function) {
  298.         final int replSize = getSize() - 1;
  299.         final List<PointValuePair> replacement = new ArrayList<>();
  300.         final double[] bestPoint = get(0).getPoint();
  301.         for (int i = 0; i < replSize; i++) {
  302.             replacement.add(Simplex.newPoint(bestPoint,
  303.                                              sigma,
  304.                                              get(i + 1).getPoint(),
  305.                                              function));
  306.         }

  307.         return replaceLast(replacement);
  308.     }

  309.     /**
  310.      * Translates the simplex such that the first point's new coordinates
  311.      * will be at the given {@code point}.
  312.      *
  313.      * @param point Coordinates of the new simplex's first point.
  314.      * @return the translated points.
  315.      * @throws DimensionMismatchException if the dimensions do not match.
  316.      */
  317.     /* package private */ Simplex translate(double[] point) {
  318.         final int dim = point.length;
  319.         if (getDimension() != dim) {
  320.             throw new DimensionMismatchException(getDimension(), dim);
  321.         }
  322.         final int len = points.size();
  323.         final double[][] coordinates = new double[len][dim];
  324.         final double[] current0 = points.get(0).getPoint(); // Current first point.

  325.         // Set new vertices.
  326.         for (int i = 0; i < len; i++) {
  327.             final double[] currentI = points.get(i).getPoint();

  328.             final double[] newI = coordinates[i];
  329.             for (int k = 0; k < dim; k++) {
  330.                 newI[k] = point[k] + currentI[k] - current0[k];
  331.             }
  332.         }

  333.         return new Simplex(coordinates);
  334.     }

  335.     /**
  336.      * Creates a new simplex where the given {@code point} replaces the one at the
  337.      * last position.
  338.      * Caveat: No check is done that the resulting set of points forms is a simplex.
  339.      *
  340.      * @param point Point.
  341.      * @return a new instance.
  342.      */
  343.     /* package private */ Simplex replaceLast(PointValuePair point) {
  344.         final List<PointValuePair> newPoints = asList(0, getDimension()); // Deep copy.
  345.         newPoints.add(new PointValuePair(point.getPoint(), // Deep copy.
  346.                                          point.getValue(),
  347.                                          false));

  348.         return new Simplex(newPoints);
  349.     }

  350.     /**
  351.      * Replace the last points of the simplex with the points from the given
  352.      * {@code replacement} list.
  353.      * Caveat: No check is done that the resulting set of points is a simplex.
  354.      *
  355.      * @param replacement List of points that will replace the last points of
  356.      * the {@code simplex}.
  357.      * @return a new instance.
  358.      */
  359.     /* package private */ Simplex replaceLast(List<PointValuePair> replacement) {
  360.         final int nPoints = replacement.size();
  361.         final int from = points.size() - nPoints;
  362.         final List<PointValuePair> newPoints = asList(0, from); // Deep copy.


  363.         for (int i = 0; i < nPoints; i++) {
  364.             final PointValuePair p = replacement.get(i);
  365.             newPoints.add(new PointValuePair(p.getPoint(), // Deep copy.
  366.                                              p.getValue(),
  367.                                              false));
  368.         }

  369.         return new Simplex(newPoints);
  370.     }

  371.     /**
  372.      * @param list List of simplex points.
  373.      * @return the centroid of the points in the given {@code list}.
  374.      */
  375.     /* package private */ static double[] centroid(List<PointValuePair> list) {
  376.         final double[] centroid = list.get(0).getPoint();

  377.         final int nPoints = list.size();
  378.         final int dim = centroid.length;
  379.         for (int i = 1; i < nPoints; i++) {
  380.             final double[] p = list.get(i).getPoint();
  381.             for (int k = 0; k < dim; k++) {
  382.                 centroid[k] += p[k];
  383.             }
  384.         }

  385.         for (int k = 0; k < dim; k++) {
  386.             centroid[k] /= nPoints;
  387.         }

  388.         return centroid;
  389.     }
  390. }