limit.java
001 /*
002  * Java Genetic Algorithm Library (jenetics-3.7.0).
003  * Copyright (c) 2007-2016 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@gmx.at)
019  */
020 package org.jenetics.engine;
021 
022 import static java.lang.Math.abs;
023 import static java.lang.Math.max;
024 import static java.lang.String.format;
025 
026 import java.time.Clock;
027 import java.time.Duration;
028 import java.util.function.BiPredicate;
029 import java.util.function.Predicate;
030 
031 import org.jenetics.internal.util.require;
032 
033 import org.jenetics.stat.DoubleMoments;
034 import org.jenetics.util.NanoClock;
035 
036 /**
037  * This class contains factory methods for creating predicates, which can be
038  * used for limiting the evolution stream.
039  *
040  @see EvolutionStream#limit(Predicate)
041  *
042  @author <a href="mailto:franz.wilhelmstoetter@gmx.at">Franz Wilhelmstötter</a>
043  @since 3.0
044  @version 3.7
045  */
046 public final class limit {
047     private limit() {require.noInstance();}
048 
049     /**
050      * Return a predicate, which will truncate the evolution stream after the
051      * given number of generations. The returned predicate behaves like a call
052      * of the {@link java.util.stream.Stream#limit(long)} and exists for
053      <i>completeness</i> reasons.
054      *
055      @since 3.1
056      *
057      @param generation the number of generations after the evolution stream is
058      *        truncated
059      @return a predicate which truncates the evolution stream after the given
060      *        number of generations
061      @throws java.lang.IllegalArgumentException if the given {@code generation}
062      *         is smaller than zero.
063      */
064     public static Predicate<Object> byFixedGeneration(final long generation) {
065         if (generation < 0) {
066             throw new IllegalArgumentException(format(
067                 "The number of generations must greater than one, but was %d",
068                 generation
069             ));
070         }
071 
072         return new Predicate<Object>() {
073             private long _current;
074             @Override
075             public boolean test(final Object o) {
076                 return ++_current < generation;
077             }
078         };
079     }
080 
081     /**
082      * Return a predicate, which will truncate the evolution stream if no
083      * better phenotype could be found after the given number of
084      * {@code generations}.
085      *
086      <pre>{@code
087      * final Phenotype<DoubleGene, Double> result = engine.stream()
088      *      // Truncate the evolution stream after 5 "steady" generations.
089      *     .limit(bySteadyFitness(5))
090      *      // The evolution will stop after maximal 100 generations.
091      *     .limit(100)
092      *     .collect(toBestPhenotype());
093      * }</pre>
094      *
095      @param generations the number of <i>steady</i> generations
096      @param <C> the fitness type
097      @return a predicate which truncate the evolution stream if no better
098      *         phenotype could be found after a give number of
099      *         {@code generations}
100      @throws IllegalArgumentException if the generation is smaller than
101      *         one.
102      */
103     public static <C extends Comparable<? super C>>
104     Predicate<EvolutionResult<?, C>> bySteadyFitness(final int generations) {
105         return new SteadyFitnessLimit<>(generations);
106     }
107 
108     /**
109      * Return a predicate, which will truncate the evolution stream if the GA
110      * execution exceeds a given time duration. This predicate is (normally)
111      * used as safety net, for guaranteed stream truncation.
112      *
113      <pre>{@code
114      * final Phenotype<DoubleGene, Double> result = engine.stream()
115      *      // Truncate the evolution stream after 5 "steady" generations.
116      *     .limit(bySteadyFitness(5))
117      *      // The evolution will stop after maximal 500 ms.
118      *     .limit(byExecutionTime(Duration.ofMillis(500), Clock.systemUTC())
119      *     .collect(toBestPhenotype());
120      * }</pre>
121      *
122      @since 3.1
123      *
124      @param duration the duration after the evolution stream will be truncated
125      @param clock the clock used for measure the execution time
126      @return a predicate, which will truncate the evolution stream, based on
127      *         the exceeded execution time
128      @throws NullPointerException if one of the arguments is {@code null}
129      */
130     public static Predicate<Object>
131     byExecutionTime(final Duration duration, final Clock clock) {
132         return new ExecutionTimeLimit(duration, clock);
133     }
134 
135     /**
136      * Return a predicate, which will truncate the evolution stream if the GA
137      * execution exceeds a given time duration. This predicate is (normally)
138      * used as safety net, for guaranteed stream truncation.
139      *
140      <pre>{@code
141      * final Phenotype<DoubleGene, Double> result = engine.stream()
142      *      // Truncate the evolution stream after 5 "steady" generations.
143      *     .limit(bySteadyFitness(5))
144      *      // The evolution will stop after maximal 500 ms.
145      *     .limit(byExecutionTime(Duration.ofMillis(500))
146      *     .collect(toBestPhenotype());
147      * }</pre>
148      *
149      @since 3.1
150      *
151      @param duration the duration after the evolution stream will be truncated
152      @return a predicate, which will truncate the evolution stream, based on
153      *         the exceeded execution time
154      @throws NullPointerException if the evolution {@code duration} is
155      *         {@code null}
156      */
157     public static Predicate<Object>
158     byExecutionTime(final Duration duration) {
159         return byExecutionTime(duration, NanoClock.systemUTC());
160     }
161 
162     /**
163      * Return a predicate, which will truncated the evolution stream if the
164      * best fitness of the current population becomes less than the specified
165      * threshold and the objective is set to minimize the fitness. This
166      * predicate also stops the evolution if the best fitness in the current
167      * population becomes greater than the user-specified fitness threshold when
168      * the objective is to maximize the fitness.
169      *
170      <pre>{@code
171      * final Phenotype<DoubleGene, Double> result = engine.stream()
172      *      // Truncate the evolution stream if the best fitness is higher than
173      *      // the given threshold of '2.3'.
174      *     .limit(byFitnessThreshold(2.3))
175      *      // The evolution will stop after maximal 250 generations; guarantees
176      *      // the termination (truncation) of the evolution stream.
177      *     .limit(250)
178      *     .collect(toBestPhenotype());
179      * }</pre>
180      *
181      @since 3.1
182      *
183      @param threshold the desired threshold
184      @param <C> the fitness type
185      @return the predicate which truncates the evolution stream based on the
186      *         given {@code threshold}.
187      @throws NullPointerException if the given {@code threshold} is
188      *        {@code null}.
189      */
190     public static <C extends Comparable<? super C>>
191     Predicate<EvolutionResult<?, C>> byFitnessThreshold(final C threshold) {
192         return new FitnessThresholdLimit<>(threshold);
193     }
194 
195     /**
196      * Return a predicate, which will truncate the evolution stream if the
197      * fitness is converging. Two filters of different lengths are used to
198      * smooth the best fitness across the generations.
199      *
200      <pre>{@code
201      * final Phenotype<DoubleGene, Double> result = engine.stream()
202      *     .limit(byFitnessConvergence(5, 15, (s, l) -> {
203      *          final double div = max(abs(s.getMean()), abs(l.getMean()));
204      *          final eps = abs(s.getMean() - l.getMean())/(div <= 10E-20 ? 1.0 : div);
205      *          return esp >= 10E-5
206      *     }))
207      *     .collect(toBestPhenotype());
208      * }</pre>
209      *
210      * In the example above, the moving average of the short- and long filter
211      * is used for determining the fitness convergence.
212      *
213      @since 3.7
214      *
215      @param shortFilterSize the size of the short filter
216      @param longFilterSize the size of the long filter. The long filter size
217      *        also determines the minimum number of generations of the evolution
218      *        stream.
219      @param proceed the predicate which determines when the evolution stream
220      *        is truncated. The first parameter of the predicate contains the
221      *        double statistics of the short filter and the second parameter
222      *        contains the statistics of the long filter
223      @param <N> the fitness type
224      @return a new fitness convergence strategy
225      @throws NullPointerException if the {@code proceed} predicate is
226      *         {@code null}
227      */
228     public static <N extends Number & Comparable<? super N>>
229     Predicate<EvolutionResult<?, N>> byFitnessConvergence(
230         final int shortFilterSize,
231         final int longFilterSize,
232         final BiPredicate<DoubleMoments, DoubleMoments> proceed
233     ) {
234         return new FitnessConvergenceLimit<>(
235             shortFilterSize,
236             longFilterSize,
237             proceed
238         );
239     }
240 
241     /**
242      * Return a predicate, which will truncate the evolution stream if the
243      * fitness is converging. Two filters of different lengths are used to
244      * smooth the best fitness across the generations. When the smoothed best
245      * fitness from the long filter is less than a user-specified percentage
246      * away from the smoothed best fitness from the short filter, the fitness is
247      * deemed as converged and the evolution terminates.
248      *
249      <pre>{@code
250      * final Phenotype<DoubleGene, Double> result = engine.stream()
251      *     .limit(byFitnessConvergence(5, 15, 10E-4))
252      *     .collect(toBestPhenotype());
253      * }</pre>
254      *
255      * In the given example, the evolution stream stops, if the difference of the
256      * mean values of the long and short filter is less than 1%. The short
257      * filter calculates the mean of the best fitness values of the last 5
258      * generations. The long filter uses the best fitness values of the last 15
259      * generations.
260      *
261      @since 3.7
262      *
263      @param shortFilterSize the size of the short filter
264      @param longFilterSize the size of the long filter. The long filter size
265      *        also determines the minimum number of generations of the evolution
266      *        stream.
267      @param epsilon the maximal relative distance of the mean value between
268      *        the short and the long filter. The {@code epsilon} must within the
269      *        range of {@code [0..1]}.
270      @param <N> the fitness type
271      @return a new fitness convergence strategy
272      @throws IllegalArgumentException if {@code shortFilterSize < 1} ||
273      *         {@code longFilterSize < 2} ||
274      *         {@code shortFilterSize >= longFilterSize}
275      @throws IllegalArgumentException if {@code epsilon} is not in the range
276      *         of {@code [0..1]}
277      */
278     public static <N extends Number & Comparable<? super N>>
279     Predicate<EvolutionResult<?, N>> byFitnessConvergence(
280         final int shortFilterSize,
281         final int longFilterSize,
282         final double epsilon
283     ) {
284         if (epsilon < 0.0 || epsilon > 1.0) {
285             throw new IllegalArgumentException(format(
286                 "The given epsilon is not in the range [0, 1]: %f", epsilon
287             ));
288         }
289 
290         return new FitnessConvergenceLimit<>(
291             shortFilterSize,
292             longFilterSize,
293             (s, l-> eps(s, l>= epsilon
294         );
295     }
296 
297     // Calculate the relative mean difference between short and long filter.
298     private static double eps(final DoubleMoments s, final DoubleMoments l) {
299         final double div = max(abs(s.getMean()), abs(l.getMean()));
300         return abs(s.getMean() - l.getMean())/(div <= 10E-20 1.0 : div);
301     }
302 
303 }