From 1354beaf452fc42c23c82fc80d2f2e9ffcbf3688 Mon Sep 17 00:00:00 2001 From: Karl Shaffer Date: Wed, 9 Aug 2023 18:06:31 -0700 Subject: Check-in commons-math 3.6.1 This includes many changes over the original version and is also incompatible, which is why the existing commons-math library is left untouched. Test: N/A Change-Id: Icd0f3069a3c62b2572f2263732d91e6c3b6e8cba --- .../direct/MultivariateFunctionPenaltyAdapter.java | 190 +++++++++++++++++++++ 1 file changed, 190 insertions(+) create mode 100644 src/main/java/org/apache/commons/math3/optimization/direct/MultivariateFunctionPenaltyAdapter.java (limited to 'src/main/java/org/apache/commons/math3/optimization/direct/MultivariateFunctionPenaltyAdapter.java') diff --git a/src/main/java/org/apache/commons/math3/optimization/direct/MultivariateFunctionPenaltyAdapter.java b/src/main/java/org/apache/commons/math3/optimization/direct/MultivariateFunctionPenaltyAdapter.java new file mode 100644 index 0000000..4946487 --- /dev/null +++ b/src/main/java/org/apache/commons/math3/optimization/direct/MultivariateFunctionPenaltyAdapter.java @@ -0,0 +1,190 @@ +/* + * 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.math3.optimization.direct; + +import org.apache.commons.math3.analysis.MultivariateFunction; +import org.apache.commons.math3.exception.DimensionMismatchException; +import org.apache.commons.math3.exception.NumberIsTooSmallException; +import org.apache.commons.math3.util.FastMath; +import org.apache.commons.math3.util.MathUtils; + +/** + *

Adapter extending bounded {@link MultivariateFunction} to an unbouded + * domain using a penalty function.

+ * + *

+ * This adapter can be used to wrap functions subject to simple bounds on + * parameters so they can be used by optimizers that do not directly + * support simple bounds. + *

+ *

+ * The principle is that the user function that will be wrapped will see its + * parameters bounded as required, i.e when its {@code value} method is called + * with argument array {@code point}, the elements array will fulfill requirement + * {@code lower[i] <= point[i] <= upper[i]} for all i. Some of the components + * may be unbounded or bounded only on one side if the corresponding bound is + * set to an infinite value. The optimizer will not manage the user function by + * itself, but it will handle this adapter and it is this adapter that will take + * care the bounds are fulfilled. The adapter {@link #value(double[])} method will + * be called by the optimizer with unbound parameters, and the adapter will check + * if the parameters is within range or not. If it is in range, then the underlying + * user function will be called, and if it is not the value of a penalty function + * will be returned instead. + *

+ *

+ * This adapter is only a poor man solution to simple bounds optimization constraints + * that can be used with simple optimizers like {@link SimplexOptimizer} with {@link + * NelderMeadSimplex} or {@link MultiDirectionalSimplex}. A better solution is to use + * an optimizer that directly supports simple bounds like {@link CMAESOptimizer} or + * {@link BOBYQAOptimizer}. One caveat of this poor man solution is that if start point + * or start simplex is completely outside of the allowed range, only the penalty function + * is used, and the optimizer may converge without ever entering the range. + *

+ * + * @see MultivariateFunctionMappingAdapter + * + * @deprecated As of 3.1 (to be removed in 4.0). + * @since 3.0 + */ + +@Deprecated +public class MultivariateFunctionPenaltyAdapter implements MultivariateFunction { + + /** Underlying bounded function. */ + private final MultivariateFunction bounded; + + /** Lower bounds. */ + private final double[] lower; + + /** Upper bounds. */ + private final double[] upper; + + /** Penalty offset. */ + private final double offset; + + /** Penalty scales. */ + private final double[] scale; + + /** Simple constructor. + *

+ * When the optimizer provided points are out of range, the value of the + * penalty function will be used instead of the value of the underlying + * function. In order for this penalty to be effective in rejecting this + * point during the optimization process, the penalty function value should + * be defined with care. This value is computed as: + *

+     *   penalty(point) = offset + ∑i[scale[i] * √|point[i]-boundary[i]|]
+     * 
+ * where indices i correspond to all the components that violates their boundaries. + *

+ *

+ * So when attempting a function minimization, offset should be larger than + * the maximum expected value of the underlying function and scale components + * should all be positive. When attempting a function maximization, offset + * should be lesser than the minimum expected value of the underlying function + * and scale components should all be negative. + * minimization, and lesser than the minimum expected value of the underlying + * function when attempting maximization. + *

+ *

+ * These choices for the penalty function have two properties. First, all out + * of range points will return a function value that is worse than the value + * returned by any in range point. Second, the penalty is worse for large + * boundaries violation than for small violations, so the optimizer has an hint + * about the direction in which it should search for acceptable points. + *

+ * @param bounded bounded function + * @param lower lower bounds for each element of the input parameters array + * (some elements may be set to {@code Double.NEGATIVE_INFINITY} for + * unbounded values) + * @param upper upper bounds for each element of the input parameters array + * (some elements may be set to {@code Double.POSITIVE_INFINITY} for + * unbounded values) + * @param offset base offset of the penalty function + * @param scale scale of the penalty function + * @exception DimensionMismatchException if lower bounds, upper bounds and + * scales are not consistent, either according to dimension or to bounadary + * values + */ + public MultivariateFunctionPenaltyAdapter(final MultivariateFunction bounded, + final double[] lower, final double[] upper, + final double offset, final double[] scale) { + + // safety checks + MathUtils.checkNotNull(lower); + MathUtils.checkNotNull(upper); + MathUtils.checkNotNull(scale); + if (lower.length != upper.length) { + throw new DimensionMismatchException(lower.length, upper.length); + } + if (lower.length != scale.length) { + throw new DimensionMismatchException(lower.length, scale.length); + } + for (int i = 0; i < lower.length; ++i) { + // note the following test is written in such a way it also fails for NaN + if (!(upper[i] >= lower[i])) { + throw new NumberIsTooSmallException(upper[i], lower[i], true); + } + } + + this.bounded = bounded; + this.lower = lower.clone(); + this.upper = upper.clone(); + this.offset = offset; + this.scale = scale.clone(); + + } + + /** Compute the underlying function value from an unbounded point. + *

+ * This method simply returns the value of the underlying function + * if the unbounded point already fulfills the bounds, and compute + * a replacement value using the offset and scale if bounds are + * violated, without calling the function at all. + *

+ * @param point unbounded point + * @return either underlying function value or penalty function value + */ + public double value(double[] point) { + + for (int i = 0; i < scale.length; ++i) { + if ((point[i] < lower[i]) || (point[i] > upper[i])) { + // bound violation starting at this component + double sum = 0; + for (int j = i; j < scale.length; ++j) { + final double overshoot; + if (point[j] < lower[j]) { + overshoot = scale[j] * (lower[j] - point[j]); + } else if (point[j] > upper[j]) { + overshoot = scale[j] * (point[j] - upper[j]); + } else { + overshoot = 0; + } + sum += FastMath.sqrt(overshoot); + } + return offset + sum; + } + } + + // all boundaries are fulfilled, we are in the expected + // domain of the underlying function + return bounded.value(point); + + } + +} -- cgit v1.2.3