001/* 002 * Java Genetic Algorithm Library (jenetics-7.2.0). 003 * Copyright (c) 2007-2023 Franz Wilhelmstötter 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 * 017 * Author: 018 * Franz Wilhelmstötter (franz.wilhelmstoetter@gmail.com) 019 */ 020package io.jenetics.engine; 021 022import static java.lang.Math.abs; 023import static java.lang.Math.max; 024import static java.lang.String.format; 025 026import java.time.Duration; 027import java.time.InstantSource; 028import java.util.concurrent.atomic.AtomicLong; 029import java.util.function.BiPredicate; 030import java.util.function.Predicate; 031 032import io.jenetics.NumericGene; 033import io.jenetics.stat.DoubleMoments; 034import io.jenetics.util.NanoClock; 035 036/** 037 * This class contains factory methods for creating predicates, which can be 038 * used for limiting the evolution stream. Some of the <em>limit</em> predicates 039 * have to maintain internal state for working properly. It is therefore 040 * recommended creating new instances for every stream and don't reuse it. 041 * 042 * @see EvolutionStream#limit(Predicate) 043 * 044 * @author <a href="mailto:franz.wilhelmstoetter@gmail.com">Franz Wilhelmstötter</a> 045 * @since 3.0 046 * @version 3.7 047 */ 048public final class Limits { 049 private Limits() {} 050 051 /** 052 * Return a predicate which always return {@code true}. 053 * 054 * @since 4.1 055 * 056 * @return a predicate which always return {@code true} 057 */ 058 public static Predicate<Object> infinite() { 059 return result -> true; 060 } 061 062 /** 063 * Return a predicate, which will truncate the evolution stream after the 064 * given number of generations. The returned predicate behaves like a call 065 * of the {@link java.util.stream.Stream#limit(long)} and exists for 066 * <i>completeness</i> reasons. 067 * 068 * @implNote 069 * This predicate is mainly there for completion reason and behaves exactly 070 * as the {@link java.util.stream.Stream#limit(long)} function, except for 071 * the number of evaluations performed by the resulting stream. The evaluation 072 * of the population is {@code max generations + 1}. This is because the 073 * limiting predicate works on the {@link EvolutionResult} object, which 074 * guarantees to contain an evaluated population. That means that the 075 * population must be evaluated at least once, even for a generation limit 076 * of zero. If this is an unacceptable performance penalty, better use the 077 * {@link java.util.stream.Stream#limit(long)} function instead. 078 * 079 * @since 3.1 080 * 081 * @param generation the number of generations after the evolution stream is 082 * truncated 083 * @return a predicate which truncates the evolution stream after the given 084 * number of generations 085 * @throws java.lang.IllegalArgumentException if the given {@code generation} 086 * is smaller than zero. 087 */ 088 public static Predicate<Object> byFixedGeneration(final long generation) { 089 if (generation < 0) { 090 throw new IllegalArgumentException(format( 091 "The number of generations must greater or equal than zero, " + 092 "but was %d", 093 generation 094 )); 095 } 096 097 return new Predicate<>() { 098 private final AtomicLong _current = new AtomicLong(); 099 @Override 100 public boolean test(final Object o) { 101 return _current.incrementAndGet() <= generation; 102 } 103 }; 104 } 105 106 /** 107 * Return a predicate, which will truncate the evolution stream if no 108 * better phenotype could be found after the given number of 109 * {@code generations}. 110 * 111 * <pre>{@code 112 * final Phenotype<DoubleGene, Double> result = engine.stream() 113 * // Truncate the evolution stream after 5 "steady" generations. 114 * .limit(bySteadyFitness(5)) 115 * // The evolution will stop after maximal 100 generations. 116 * .limit(100) 117 * .collect(toBestPhenotype()); 118 * }</pre> 119 * 120 * @param generations the number of <i>steady</i> generations 121 * @param <C> the fitness type 122 * @return a predicate which truncate the evolution stream if no better 123 * phenotype could be found after a give number of 124 * {@code generations} 125 * @throws IllegalArgumentException if the generation is smaller than 126 * one. 127 */ 128 public static <C extends Comparable<? super C>> 129 Predicate<EvolutionResult<?, C>> bySteadyFitness(final int generations) { 130 return new SteadyFitnessLimit<>(generations); 131 } 132 133 /** 134 * Return a predicate, which will truncate the evolution stream if the GA 135 * execution exceeds a given time duration. This predicate is (normally) 136 * used as a safety net, for guaranteed stream truncation. 137 * 138 * <pre>{@code 139 * final Phenotype<DoubleGene, Double> result = engine.stream() 140 * // Truncate the evolution stream after 5 "steady" generations. 141 * .limit(bySteadyFitness(5)) 142 * // The evolution will stop after maximal 500 ms. 143 * .limit(byExecutionTime(Duration.ofMillis(500), Clock.systemUTC()) 144 * .collect(toBestPhenotype()); 145 * }</pre> 146 * 147 * @since 3.1 148 * 149 * @param duration the duration after the evolution stream will be truncated 150 * @param clock the clock used for measure the execution time 151 * @return a predicate, which will truncate the evolution stream, based on 152 * the exceeded execution time 153 * @throws NullPointerException if one of the arguments is {@code null} 154 */ 155 public static Predicate<Object> 156 byExecutionTime(final Duration duration, final InstantSource clock) { 157 return new ExecutionTimeLimit(duration, clock); 158 } 159 160 /** 161 * Return a predicate, which will truncate the evolution stream if the GA 162 * execution exceeds a given time duration. This predicate is (normally) 163 * used as a safety net, for guaranteed stream truncation. 164 * 165 * <pre>{@code 166 * final Phenotype<DoubleGene, Double> result = engine.stream() 167 * // Truncate the evolution stream after 5 "steady" generations. 168 * .limit(bySteadyFitness(5)) 169 * // The evolution will stop after maximal 500 ms. 170 * .limit(byExecutionTime(Duration.ofMillis(500)) 171 * .collect(toBestPhenotype()); 172 * }</pre> 173 * 174 * @since 3.1 175 * 176 * @param duration the duration after the evolution stream will be truncated 177 * @return a predicate, which will truncate the evolution stream, based on 178 * the exceeded execution time 179 * @throws NullPointerException if the evolution {@code duration} is 180 * {@code null} 181 */ 182 public static Predicate<Object> 183 byExecutionTime(final Duration duration) { 184 return byExecutionTime(duration, NanoClock.systemUTC()); 185 } 186 187 /** 188 * Return a predicate, which will truncated the evolution stream if the 189 * best fitness of the current population becomes less than the specified 190 * threshold, and the objective is set to minimize the fitness. This 191 * predicate also stops the evolution if the best fitness in the current 192 * population becomes greater than the user-specified fitness threshold when 193 * the objective is to maximize the fitness. 194 * 195 * <pre>{@code 196 * final Phenotype<DoubleGene, Double> result = engine.stream() 197 * // Truncate the evolution stream if the best fitness is higher than 198 * // the given threshold of '2.3'. 199 * .limit(byFitnessThreshold(2.3)) 200 * // The evolution will stop after maximal 250 generations; guarantees 201 * // the termination (truncation) of the evolution stream. 202 * .limit(250) 203 * .collect(toBestPhenotype()); 204 * }</pre> 205 * 206 * @since 3.1 207 * 208 * @param threshold the desired threshold 209 * @param <C> the fitness type 210 * @return the predicate which truncates the evolution stream based on the 211 * given {@code threshold}. 212 * @throws NullPointerException if the given {@code threshold} is 213 * {@code null}. 214 */ 215 public static <C extends Comparable<? super C>> 216 Predicate<EvolutionResult<?, C>> byFitnessThreshold(final C threshold) { 217 return new FitnessThresholdLimit<>(threshold); 218 } 219 220 /** 221 * Return a predicate, which will truncate the evolution stream if the 222 * fitness is converging. Two filters of different lengths are used to 223 * smooth the best fitness across the generations. 224 * 225 * <pre>{@code 226 * final Phenotype<DoubleGene, Double> result = engine.stream() 227 * .limit(byFitnessConvergence(5, 15, (s, l) -> { 228 * final double div = max(abs(s.getMean()), abs(l.getMean())); 229 * final eps = abs(s.getMean() - l.getMean())/(div <= 10E-20 ? 1.0 : div); 230 * return eps >= 10E-5 231 * })) 232 * .collect(toBestPhenotype()); 233 * }</pre> 234 * 235 * In the example above, the moving average of the short- and long filter 236 * is used for determining the fitness convergence. 237 * 238 * @apiNote The returned predicate maintains a mutable state. 239 * Using it in a parallel evolution streams needs external synchronization 240 * of the {@code test} method. 241 * 242 * @since 3.7 243 * 244 * @param shortFilterSize the size of the short filter 245 * @param longFilterSize the size of the long filter. The long filter size 246 * also determines the minimum number of generations of the evolution 247 * stream. 248 * @param proceed the predicate which determines when the evolution stream 249 * is truncated. The first parameter of the predicate contains the 250 * double statistics of the short filter, and the second parameter 251 * contains the statistics of the long filter 252 * @param <N> the fitness type 253 * @return a new fitness convergence strategy 254 * @throws NullPointerException if the {@code proceed} predicate is 255 * {@code null} 256 */ 257 public static <N extends Number & Comparable<? super N>> 258 Predicate<EvolutionResult<?, N>> byFitnessConvergence( 259 final int shortFilterSize, 260 final int longFilterSize, 261 final BiPredicate<DoubleMoments, DoubleMoments> proceed 262 ) { 263 return new FitnessConvergenceLimit<>( 264 shortFilterSize, 265 longFilterSize, 266 proceed 267 ); 268 } 269 270 /** 271 * Return a predicate, which will truncate the evolution stream if the 272 * fitness is converging. Two filters of different lengths are used to 273 * smooth the best fitness across the generations. When the smoothed best 274 * fitness from the long filter is less than a user-specified percentage 275 * away from the smoothed best fitness from the short filter, the fitness is 276 * deemed as converged and the evolution terminates. 277 * 278 * <pre>{@code 279 * final Phenotype<DoubleGene, Double> result = engine.stream() 280 * .limit(byFitnessConvergence(5, 15, 10E-4)) 281 * .collect(toBestPhenotype()); 282 * }</pre> 283 * 284 * In the given example, the evolution stream stops, if the difference of the 285 * mean values of the long and short filter is less than 1%. The short 286 * filter calculates the mean of the best fitness values of the last 5 287 * generations. The long filter uses the best fitness values of the last 15 288 * generations. 289 * 290 * @apiNote The returned predicate maintains a mutable state. 291 * Using it in a parallel evolution streams needs external synchronization 292 * of the {@code test} method. 293 * 294 * @since 3.7 295 * 296 * @param shortFilterSize the size of the short filter 297 * @param longFilterSize the size of the long filter. The long filter size 298 * also determines the minimum number of generations of the evolution 299 * stream. 300 * @param epsilon the maximal relative distance of the mean value between 301 * the short and the long filter. The {@code epsilon} must within the 302 * range of {@code [0..1]}. 303 * @param <N> the fitness type 304 * @return a new fitness convergence strategy 305 * @throws IllegalArgumentException if {@code shortFilterSize < 1} || 306 * {@code longFilterSize < 2} || 307 * {@code shortFilterSize >= longFilterSize} 308 * @throws IllegalArgumentException if {@code epsilon} is not in the range 309 * of {@code [0..1]} 310 */ 311 public static <N extends Number & Comparable<? super N>> 312 Predicate<EvolutionResult<?, N>> byFitnessConvergence( 313 final int shortFilterSize, 314 final int longFilterSize, 315 final double epsilon 316 ) { 317 if (epsilon < 0.0 || epsilon > 1.0) { 318 throw new IllegalArgumentException(format( 319 "The given epsilon is not in the range [0, 1]: %f", epsilon 320 )); 321 } 322 323 return new FitnessConvergenceLimit<>( 324 shortFilterSize, 325 longFilterSize, 326 (s, l) -> eps(s.mean(), l.mean()) >= epsilon 327 ); 328 } 329 330 // Calculate the relative mean difference between short and long filter. 331 private static double eps(final double s, final double l) { 332 final double div = max(abs(s), abs(l)); 333 return abs(s - l)/(div <= 10E-20 ? 1.0 : div); 334 } 335 336 /** 337 * A termination method that stops the evolution when the population is 338 * deemed as converged. The population is deemed as converged when the 339 * average fitness across the current population is less than a 340 * user-specified percentage away from the best fitness of the current 341 * population. This method takes a predicate with the <em>best</em> fitness 342 * and the population fitness moments and determine whether to proceed or 343 * not. 344 * 345 * @since 3.9 346 * 347 * @param proceed the predicate which determines when the evolution stream 348 * is truncated. The first parameter of the predicate contains the 349 * best fitness of the population, and the second parameter contains 350 * the statistics of population fitness values 351 * @param <N> the fitness type 352 * @return a new fitness convergence strategy 353 * @throws NullPointerException if the {@code proceed} predicate is 354 * {@code null} 355 */ 356 public static <N extends Number & Comparable<? super N>> 357 Predicate<EvolutionResult<?, N>> byPopulationConvergence( 358 final BiPredicate<Double, DoubleMoments> proceed 359 ) { 360 return new PopulationConvergenceLimit<>(proceed); 361 } 362 363 /** 364 * A termination method that stops the evolution when the population is 365 * deemed as converged. The population is deemed as converged when the 366 * average fitness across the current population is less than a 367 * user-specified percentage away from the best fitness of the current 368 * population. 369 * 370 * @since 3.9 371 * 372 * @param epsilon the maximal relative distance of the best fitness value of 373 * the population and the mean value of the population fitness values. 374 * @param <N> the fitness type 375 * @return a new fitness convergence strategy 376 * @throws IllegalArgumentException if {@code epsilon} is not in the range 377 * of {@code [0..1]} 378 */ 379 public static <N extends Number & Comparable<? super N>> 380 Predicate<EvolutionResult<?, N>> 381 byPopulationConvergence(final double epsilon) { 382 if (epsilon < 0.0 || epsilon > 1.0) { 383 throw new IllegalArgumentException(format( 384 "The given epsilon is not in the range [0, 1]: %f", epsilon 385 )); 386 } 387 388 return new PopulationConvergenceLimit<>((best, moments) -> 389 eps(best, moments.mean()) >= epsilon 390 ); 391 } 392 393 /** 394 * A termination method that stops the evolution when a user-specified 395 * percentage of the genes ({@code convergedGeneRage}) that make up a 396 * {@code Genotype} are deemed as converged. A gene is deemed as converged, 397 * if the {@code geneConvergence} {@code Predicate<DoubleMoments>} for this 398 * gene returns {@code true}. 399 * 400 * @since 4.0 401 * @see #byGeneConvergence(double, double) 402 * 403 * @param geneConvergence predicate, which defines when a gene is deemed as 404 * converged, by using the statistics of this gene over all genotypes 405 * of the population 406 * @param convergedGeneRate the percentage of genes which must be converged 407 * for truncating the evolution stream 408 * @param <G> the gene type 409 * @return a new gene convergence predicate 410 * @throws NullPointerException if the given gene convergence predicate is 411 * {@code null} 412 * @throws IllegalArgumentException if the {@code convergedGeneRate} is not 413 * within the range {@code [0, 1]} 414 */ 415 public static <G extends NumericGene<?, G>> Predicate<EvolutionResult<G, ?>> 416 byGeneConvergence( 417 final Predicate<DoubleMoments> geneConvergence, 418 final double convergedGeneRate 419 ) { 420 return new GeneConvergenceLimit<>(geneConvergence, convergedGeneRate); 421 } 422 423 /** 424 * A termination method that stops the evolution when a user-specified 425 * percentage of the genes ({@code convergedGeneRage}) that make up a 426 * {@code Genotype} are deemed as converged. A gene is deemed as converged 427 * when the average value of that gene across all the genotypes in the 428 * current population is less than a user-specified percentage 429 * ({@code convergenceRate}) away from the maximum gene value across the 430 * genotypes. 431 * <p> 432 * This method is equivalent to the following code snippet: 433 * <pre>{@code 434 * final Predicate<EvolutionResult<DoubleGene, ?>> limit = 435 * byGeneConvergence( 436 * stat -> stat.getMax()*convergenceRate <= stat.getMean(), 437 * convergedGeneRate 438 * ); 439 * }</pre> 440 * 441 * @since 4.0 442 * @see #byGeneConvergence(Predicate, double) 443 * 444 * @param convergenceRate the relative distance of the average gene value 445 * to its maximum value 446 * @param convergedGeneRate the percentage of genes which must be converged 447 * for truncating the evolution stream 448 * @param <G> the gene type 449 * @return a new gene convergence predicate 450 * @throws IllegalArgumentException if the {@code convergedGeneRate} or 451 * {@code convergenceRate} are not within the range {@code [0, 1]} 452 */ 453 public static <G extends NumericGene<?, G>> Predicate<EvolutionResult<G, ?>> 454 byGeneConvergence( 455 final double convergenceRate, 456 final double convergedGeneRate 457 ) { 458 return byGeneConvergence( 459 stat -> stat.max()*convergenceRate <= stat.mean(), 460 convergedGeneRate 461 ); 462 } 463 464}