View Javadoc
1   package net.bmahe.genetics4j.core.termination;
2   
3   import java.time.Duration;
4   import java.util.Arrays;
5   import java.util.Comparator;
6   import java.util.List;
7   import java.util.Objects;
8   import java.util.Optional;
9   
10  import org.apache.commons.lang3.Validate;
11  
12  import net.bmahe.genetics4j.core.Genotype;
13  import net.bmahe.genetics4j.core.spec.AbstractEAConfiguration;
14  
15  /**
16   * Utility class providing factory methods for creating common termination conditions in evolutionary algorithms.
17   * 
18   * <p>Terminations provides a comprehensive set of pre-built termination criteria that can be used individually or
19   * combined to create complex stopping conditions for evolutionary algorithms. Each method returns a {@link Termination}
20   * instance that encapsulates the specific logic for determining when evolution should stop.
21   * 
22   * <p>Available termination criteria include:
23   * <ul>
24   * <li><strong>Generation-based</strong>: Stop after a maximum number of generations</li>
25   * <li><strong>Time-based</strong>: Stop after a specified duration has elapsed</li>
26   * <li><strong>Fitness-based</strong>: Stop when fitness reaches certain thresholds</li>
27   * <li><strong>Convergence-based</strong>: Stop when fitness stops improving for a period</li>
28   * <li><strong>Logical combinations</strong>: Combine multiple criteria with AND/OR logic</li>
29   * </ul>
30   * 
31   * <p>Termination criteria can be combined to create sophisticated stopping conditions:
32   * 
33   * <pre>{@code
34   * // Stop after 100 generations OR when fitness reaches 0.95 OR after 5 minutes
35   * Termination<Double> complexTermination = Terminations.or(
36   * 		Terminations.ofMaxGeneration(100),
37   * 		Terminations.ofFitnessAtLeast(0.95),
38   * 		Terminations.ofMaxTime(Duration.ofMinutes(5)));
39   * 
40   * // Stop only when BOTH conditions are met: good fitness AND stable evolution
41   * Termination<Double> conservativeTermination = Terminations
42   * 		.and(Terminations.ofFitnessAtLeast(0.9), Terminations.ofStableFitness(20));
43   * 
44   * // Simple generation limit for quick experiments
45   * Termination<Double> simpleTermination = Terminations.ofMaxGeneration(50);
46   * }</pre>
47   * 
48   * <p>Common usage patterns:
49   * <ul>
50   * <li><strong>Development and testing</strong>: Use generation limits for quick experimentation</li>
51   * <li><strong>Production systems</strong>: Combine time limits with fitness criteria for reliability</li>
52   * <li><strong>Research applications</strong>: Use convergence detection to study algorithm behavior</li>
53   * <li><strong>Resource-constrained environments</strong>: Use time-based limits for predictable execution</li>
54   * </ul>
55   * 
56   * <p>Design considerations:
57   * <ul>
58   * <li><strong>Performance</strong>: Termination checks are called frequently; implementations are optimized</li>
59   * <li><strong>Thread safety</strong>: Some termination criteria maintain internal state safely</li>
60   * <li><strong>Flexibility</strong>: All criteria can be combined using logical operators</li>
61   * <li><strong>Reliability</strong>: Include fallback termination criteria to prevent infinite loops</li>
62   * </ul>
63   * 
64   * @see Termination
65   * @see net.bmahe.genetics4j.core.EASystem
66   * @see net.bmahe.genetics4j.core.spec.EAExecutionContext
67   */
68  public class Terminations {
69  
70  	/**
71  	 * Creates a termination condition that stops evolution after a specified number of generations.
72  	 * 
73  	 * <p>This is the most common termination criterion, providing a simple upper bound on the number of evolutionary
74  	 * cycles. The algorithm will terminate when the generation counter reaches or exceeds the specified maximum.
75  	 * 
76  	 * @param <T>           the type of fitness values in the evolutionary algorithm
77  	 * @param maxGeneration the maximum number of generations to run (must be positive)
78  	 * @return a termination condition that stops after the specified number of generations
79  	 * @throws IllegalArgumentException if maxGeneration is not positive
80  	 */
81  	public static <T extends Comparable<T>> Termination<T> ofMaxGeneration(final long maxGeneration) {
82  		Validate.isTrue(maxGeneration > 0);
83  
84  		return new Termination<>() {
85  
86  			@Override
87  			public boolean isDone(final AbstractEAConfiguration<T> eaConfiguration, final long generation,
88  					final List<Genotype> population, final List<T> fitness) {
89  				Validate.isTrue(generation >= 0);
90  
91  				return generation >= maxGeneration;
92  			}
93  		};
94  	}
95  
96  	/**
97  	 * Creates a termination condition that stops evolution after a specified time duration.
98  	 * 
99  	 * <p>This time-based termination is useful for ensuring predictable execution times, especially in production
100 	 * environments or when computational resources are limited. The timer starts on the first evaluation and stops when
101 	 * the elapsed time exceeds the specified duration.
102 	 * 
103 	 * @param <T>      the type of fitness values in the evolutionary algorithm
104 	 * @param duration the maximum time to run the algorithm
105 	 * @return a termination condition that stops after the specified duration
106 	 * @throws IllegalArgumentException if duration is null
107 	 */
108 	public static <T extends Comparable<T>> Termination<T> ofMaxTime(final Duration duration) {
109 		Objects.requireNonNull(duration);
110 
111 		return new Termination<>() {
112 
113 			private final long durationNanos = duration.toNanos();
114 			private Long startTime;
115 
116 			@Override
117 			public boolean isDone(final AbstractEAConfiguration<T> eaConfiguration, final long generation,
118 					final List<Genotype> population, final List<T> fitness) {
119 				Validate.isTrue(generation >= 0);
120 
121 				final long nowNanos = System.nanoTime();
122 
123 				if (startTime == null) {
124 					startTime = nowNanos;
125 				}
126 
127 				return (nowNanos - startTime) >= durationNanos;
128 			}
129 		};
130 	}
131 
132 	/**
133 	 * Creates a termination condition that requires ALL specified conditions to be met.
134 	 * 
135 	 * <p>This logical AND operation creates a conservative termination strategy where evolution continues until every
136 	 * provided termination criterion is satisfied. Useful for ensuring multiple quality conditions are met before
137 	 * stopping.
138 	 * 
139 	 * @param <T>          the type of fitness values in the evolutionary algorithm
140 	 * @param terminations the termination conditions that must all be satisfied
141 	 * @return a termination condition that stops only when all conditions are met
142 	 * @throws IllegalArgumentException if terminations is null or empty
143 	 */
144 	@SafeVarargs
145 	public static <T extends Comparable<T>> Termination<T> and(final Termination<T>... terminations) {
146 		Objects.requireNonNull(terminations);
147 		Validate.isTrue(terminations.length > 0);
148 
149 		return new Termination<>() {
150 
151 			@Override
152 			public boolean isDone(final AbstractEAConfiguration<T> eaConfiguration, final long generation,
153 					final List<Genotype> population, final List<T> fitness) {
154 				return Arrays.stream(terminations)
155 						.allMatch(termination -> termination.isDone(eaConfiguration, generation, population, fitness));
156 			}
157 
158 		};
159 	}
160 
161 	/**
162 	 * Creates a termination condition that stops when ANY of the specified conditions is met.
163 	 * 
164 	 * <p>This logical OR operation creates a flexible termination strategy where evolution stops as soon as any one of
165 	 * the provided criteria is satisfied. Commonly used to provide multiple stopping conditions like time limits,
166 	 * generation limits, or fitness thresholds.
167 	 * 
168 	 * @param <T>          the type of fitness values in the evolutionary algorithm
169 	 * @param terminations the termination conditions, any of which can trigger stopping
170 	 * @return a termination condition that stops when any condition is met
171 	 * @throws IllegalArgumentException if terminations is null or empty
172 	 */
173 	@SafeVarargs
174 	public static <T extends Comparable<T>> Termination<T> or(final Termination<T>... terminations) {
175 		Objects.requireNonNull(terminations);
176 		Validate.isTrue(terminations.length > 0);
177 
178 		return new Termination<>() {
179 
180 			@Override
181 			public boolean isDone(final AbstractEAConfiguration<T> eaConfiguration, final long generation,
182 					final List<Genotype> population, final List<T> fitness) {
183 				return Arrays.stream(terminations)
184 						.anyMatch(termination -> termination.isDone(eaConfiguration, generation, population, fitness));
185 			}
186 
187 		};
188 	}
189 
190 	/**
191 	 * Creates a termination condition that stops when any individual reaches a minimum fitness threshold.
192 	 * 
193 	 * <p>This fitness-based termination is useful for maximization problems where you want to stop as soon as a solution
194 	 * of acceptable quality is found. The condition is satisfied when any individual in the population has a fitness
195 	 * value greater than or equal to the threshold.
196 	 * 
197 	 * @param <T>       the type of fitness values in the evolutionary algorithm
198 	 * @param threshold the minimum fitness value required to trigger termination
199 	 * @return a termination condition that stops when fitness reaches the threshold
200 	 * @throws IllegalArgumentException if threshold is null
201 	 */
202 	public static <T extends Comparable<T>> Termination<T> ofFitnessAtLeast(final T threshold) {
203 		Objects.requireNonNull(threshold);
204 		return new Termination<>() {
205 
206 			@Override
207 			public boolean isDone(final AbstractEAConfiguration<T> eaConfiguration, final long generation,
208 					final List<Genotype> population, final List<T> fitness) {
209 				Validate.isTrue(generation >= 0);
210 
211 				return fitness.stream().anyMatch(fitnessValue -> threshold.compareTo(fitnessValue) <= 0);
212 			}
213 		};
214 	}
215 
216 	/**
217 	 * Creates a termination condition that stops when any individual reaches a maximum fitness threshold.
218 	 * 
219 	 * <p>This fitness-based termination is useful for minimization problems where you want to stop as soon as a solution
220 	 * of acceptable quality is found. The condition is satisfied when any individual in the population has a fitness
221 	 * value less than or equal to the threshold.
222 	 * 
223 	 * @param <T>       the type of fitness values in the evolutionary algorithm
224 	 * @param threshold the maximum fitness value required to trigger termination
225 	 * @return a termination condition that stops when fitness reaches the threshold
226 	 * @throws IllegalArgumentException if threshold is null
227 	 */
228 	public static <T extends Comparable<T>> Termination<T> ofFitnessAtMost(final T threshold) {
229 		Objects.requireNonNull(threshold);
230 		return new Termination<>() {
231 
232 			@Override
233 			public boolean isDone(final AbstractEAConfiguration<T> eaConfiguration, final long generation,
234 					final List<Genotype> population, final List<T> fitness) {
235 				Validate.isTrue(generation >= 0);
236 
237 				return fitness.stream().anyMatch(fitnessValue -> threshold.compareTo(fitnessValue) >= 0);
238 			}
239 		};
240 	}
241 
242 	/**
243 	 * Creates a termination condition that stops when fitness stops improving for a specified number of generations.
244 	 * 
245 	 * <p>This convergence-based termination detects when the evolutionary algorithm has reached a stable state where
246 	 * further evolution is unlikely to yield significant improvements. It tracks the best fitness value and stops
247 	 * evolution if no improvement is observed for the specified number of consecutive generations.
248 	 * 
249 	 * <p>This termination criterion is particularly useful for:
250 	 * <ul>
251 	 * <li>Preventing unnecessary computation when the algorithm has converged</li>
252 	 * <li>Automatically adapting to problem difficulty</li>
253 	 * <li>Research applications studying convergence behavior</li>
254 	 * </ul>
255 	 * 
256 	 * @param <T>                    the type of fitness values in the evolutionary algorithm
257 	 * @param stableGenerationsCount the number of generations without improvement required to trigger termination
258 	 * @return a termination condition that stops when fitness plateaus
259 	 * @throws IllegalArgumentException if stableGenerationsCount is not positive
260 	 */
261 	public static <T extends Comparable<T>> Termination<T> ofStableFitness(final int stableGenerationsCount) {
262 		Validate.isTrue(stableGenerationsCount > 0);
263 
264 		return new Termination<>() {
265 
266 			private long lastImprovedGeneration = -1;
267 			private T lastBestFitness = null;
268 
269 			@Override
270 			public boolean isDone(final AbstractEAConfiguration<T> eaConfiguration, final long generation,
271 					final List<Genotype> population, final List<T> fitness) {
272 				Validate.isTrue(generation >= 0);
273 
274 				final Comparator<T> fitnessComparator = eaConfiguration.fitnessComparator();
275 
276 				final Optional<T> bestFitnessOpt = fitness.stream().max(fitnessComparator);
277 
278 				if (lastImprovedGeneration < 0
279 						|| bestFitnessOpt.map(bestFitness -> fitnessComparator.compare(bestFitness, lastBestFitness) > 0)
280 								.orElse(false)) {
281 					lastImprovedGeneration = generation;
282 					lastBestFitness = bestFitnessOpt.get();
283 				}
284 				return generation - lastImprovedGeneration > stableGenerationsCount;
285 			}
286 		};
287 	}
288 }