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 * https://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 18 /* 19 * This is not the original file distributed by the Apache Software Foundation 20 * It has been modified by the Hipparchus project 21 */ 22 package org.hipparchus.optim.nonlinear.scalar; 23 24 import org.hipparchus.analysis.MultivariateFunction; 25 import org.hipparchus.exception.LocalizedCoreFormats; 26 import org.hipparchus.exception.MathIllegalArgumentException; 27 import org.hipparchus.util.FastMath; 28 import org.hipparchus.util.MathUtils; 29 30 /** 31 * <p>Adapter extending bounded {@link MultivariateFunction} to an unbouded 32 * domain using a penalty function.</p> 33 * 34 * <p> 35 * This adapter can be used to wrap functions subject to simple bounds on 36 * parameters so they can be used by optimizers that do <em>not</em> directly 37 * support simple bounds. 38 * </p> 39 * <p> 40 * The principle is that the user function that will be wrapped will see its 41 * parameters bounded as required, i.e when its {@code value} method is called 42 * with argument array {@code point}, the elements array will fulfill requirement 43 * {@code lower[i] <= point[i] <= upper[i]} for all i. Some of the components 44 * may be unbounded or bounded only on one side if the corresponding bound is 45 * set to an infinite value. The optimizer will not manage the user function by 46 * itself, but it will handle this adapter and it is this adapter that will take 47 * care the bounds are fulfilled. The adapter {@link #value(double[])} method will 48 * be called by the optimizer with unbound parameters, and the adapter will check 49 * if the parameters is within range or not. If it is in range, then the underlying 50 * user function will be called, and if it is not the value of a penalty function 51 * will be returned instead. 52 * </p> 53 * <p> 54 * This adapter is only a poor-man's solution to simple bounds optimization 55 * constraints that can be used with simple optimizers like 56 * {@link org.hipparchus.optim.nonlinear.scalar.noderiv.SimplexOptimizer 57 * SimplexOptimizer}. 58 * A better solution is to use an optimizer that directly supports simple bounds like 59 * {@link org.hipparchus.optim.nonlinear.scalar.noderiv.CMAESOptimizer 60 * CMAESOptimizer} or 61 * {@link org.hipparchus.optim.nonlinear.scalar.noderiv.BOBYQAOptimizer 62 * BOBYQAOptimizer}. 63 * One caveat of this poor-man's solution is that if start point or start simplex 64 * is completely outside of the allowed range, only the penalty function is used, 65 * and the optimizer may converge without ever entering the range. 66 * </p> 67 * 68 * @see MultivariateFunctionMappingAdapter 69 * 70 */ 71 public class MultivariateFunctionPenaltyAdapter 72 implements MultivariateFunction { 73 /** Underlying bounded function. */ 74 private final MultivariateFunction bounded; 75 /** Lower bounds. */ 76 private final double[] lower; 77 /** Upper bounds. */ 78 private final double[] upper; 79 /** Penalty offset. */ 80 private final double offset; 81 /** Penalty scales. */ 82 private final double[] scale; 83 84 /** 85 * Simple constructor. 86 * <p> 87 * When the optimizer provided points are out of range, the value of the 88 * penalty function will be used instead of the value of the underlying 89 * function. In order for this penalty to be effective in rejecting this 90 * point during the optimization process, the penalty function value should 91 * be defined with care. This value is computed as:</p> 92 * <p> 93 * penalty(point) = offset + ∑<sub>i</sub>[scale[i] * √|point[i]-boundary[i]|] 94 * </p> 95 * <p> 96 * where indices i correspond to all the components that violates their boundaries. 97 * </p> 98 * <p> 99 * So when attempting a function minimization, offset should be larger than 100 * the maximum expected value of the underlying function and scale components 101 * should all be positive. When attempting a function maximization, offset 102 * should be lesser than the minimum expected value of the underlying function 103 * and scale components should all be negative. 104 * minimization, and lesser than the minimum expected value of the underlying 105 * function when attempting maximization. 106 * </p> 107 * <p> 108 * These choices for the penalty function have two properties. First, all out 109 * of range points will return a function value that is worse than the value 110 * returned by any in range point. Second, the penalty is worse for large 111 * boundaries violation than for small violations, so the optimizer has an hint 112 * about the direction in which it should search for acceptable points. 113 * </p> 114 * @param bounded bounded function 115 * @param lower lower bounds for each element of the input parameters array 116 * (some elements may be set to {@code Double.NEGATIVE_INFINITY} for 117 * unbounded values) 118 * @param upper upper bounds for each element of the input parameters array 119 * (some elements may be set to {@code Double.POSITIVE_INFINITY} for 120 * unbounded values) 121 * @param offset base offset of the penalty function 122 * @param scale scale of the penalty function 123 * @exception MathIllegalArgumentException if lower bounds, upper bounds and 124 * scales are not consistent, either according to dimension or to bounadary 125 * values 126 */ 127 public MultivariateFunctionPenaltyAdapter(final MultivariateFunction bounded, 128 final double[] lower, final double[] upper, 129 final double offset, final double[] scale) { 130 131 // safety checks 132 MathUtils.checkNotNull(lower); 133 MathUtils.checkNotNull(upper); 134 MathUtils.checkNotNull(scale); 135 if (lower.length != upper.length) { 136 throw new MathIllegalArgumentException(LocalizedCoreFormats.DIMENSIONS_MISMATCH, 137 lower.length, upper.length); 138 } 139 if (lower.length != scale.length) { 140 throw new MathIllegalArgumentException(LocalizedCoreFormats.DIMENSIONS_MISMATCH, 141 lower.length, scale.length); 142 } 143 for (int i = 0; i < lower.length; ++i) { 144 if (!(upper[i] >= lower[i])) { // NOPMD - the test is written in such a way it also fails for NaN 145 throw new MathIllegalArgumentException(LocalizedCoreFormats.NUMBER_TOO_SMALL, 146 upper[i], lower[i]); 147 } 148 } 149 150 this.bounded = bounded; 151 this.lower = lower.clone(); 152 this.upper = upper.clone(); 153 this.offset = offset; 154 this.scale = scale.clone(); 155 } 156 157 /** 158 * Computes the underlying function value from an unbounded point. 159 * <p> 160 * This method simply returns the value of the underlying function 161 * if the unbounded point already fulfills the bounds, and compute 162 * a replacement value using the offset and scale if bounds are 163 * violated, without calling the function at all. 164 * </p> 165 * @param point unbounded point 166 * @return either underlying function value or penalty function value 167 */ 168 @Override 169 public double value(double[] point) { 170 171 for (int i = 0; i < scale.length; ++i) { 172 if ((point[i] < lower[i]) || (point[i] > upper[i])) { 173 // bound violation starting at this component 174 double sum = 0; 175 for (int j = i; j < scale.length; ++j) { 176 final double overshoot; 177 if (point[j] < lower[j]) { 178 overshoot = scale[j] * (lower[j] - point[j]); 179 } else if (point[j] > upper[j]) { 180 overshoot = scale[j] * (point[j] - upper[j]); 181 } else { 182 overshoot = 0; 183 } 184 sum += FastMath.sqrt(overshoot); 185 } 186 return offset + sum; 187 } 188 } 189 190 // all boundaries are fulfilled, we are in the expected 191 // domain of the underlying function 192 return bounded.value(point); 193 } 194 }