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 }
|