View Javadoc
1   package net.bmahe.genetics4j.core;
2   
3   import java.util.ArrayList;
4   import java.util.List;
5   import java.util.Objects;
6   import java.util.Optional;
7   import java.util.concurrent.ExecutorService;
8   import java.util.concurrent.ForkJoinPool;
9   import java.util.stream.Collectors;
10  
11  import net.bmahe.genetics4j.core.combination.ChromosomeCombinator;
12  import net.bmahe.genetics4j.core.combination.ChromosomeCombinatorResolver;
13  import net.bmahe.genetics4j.core.evaluation.FitnessEvaluator;
14  import net.bmahe.genetics4j.core.evaluation.FitnessEvaluatorBulkAsync;
15  import net.bmahe.genetics4j.core.evaluation.FitnessEvaluatorSync;
16  import net.bmahe.genetics4j.core.evaluation.FitnessEvaluatorVirtualThread;
17  import net.bmahe.genetics4j.core.mutation.MutationPolicyHandlerResolver;
18  import net.bmahe.genetics4j.core.mutation.Mutator;
19  import net.bmahe.genetics4j.core.replacement.ReplacementStrategyHandler;
20  import net.bmahe.genetics4j.core.selection.SelectionPolicyHandlerResolver;
21  import net.bmahe.genetics4j.core.spec.AbstractEAConfiguration;
22  import net.bmahe.genetics4j.core.spec.AbstractEAExecutionContext;
23  import net.bmahe.genetics4j.core.spec.EAConfiguration;
24  import net.bmahe.genetics4j.core.spec.EAConfigurationBulkAsync;
25  import net.bmahe.genetics4j.core.spec.EAExecutionContext;
26  import net.bmahe.genetics4j.core.spec.combination.CombinationPolicy;
27  import net.bmahe.genetics4j.core.spec.mutation.MutationPolicy;
28  
29  /**
30   * Factory class providing convenient methods for creating properly configured {@link EASystem} instances.
31   * 
32   * <p>EASystemFactory simplifies the complex process of assembling all the components required for evolutionary
33   * algorithms by providing pre-configured factory methods that handle the initialization and wiring of selection
34   * strategies, mutation operators, combination policies, and fitness evaluators.
35   * 
36   * <p>The factory abstracts away the complexity of manually creating and configuring:
37   * <ul>
38   * <li><strong>Selection policy resolvers</strong>: Components that match selection strategies to implementations</li>
39   * <li><strong>Mutation policy resolvers</strong>: Components that handle different mutation strategies</li>
40   * <li><strong>Chromosome combinators</strong>: Components responsible for crossover operations</li>
41   * <li><strong>Replacement strategy handlers</strong>: Components managing population replacement</li>
42   * <li><strong>Fitness evaluators</strong>: Components handling fitness computation strategies</li>
43   * </ul>
44   * 
45   * <p>Factory methods are organized by evaluation strategy:
46   * <ul>
47   * <li><strong>Synchronous evaluation</strong>: Traditional single-threaded or parallel evaluation using
48   * {@link EAConfiguration}</li>
49   * <li><strong>Bulk asynchronous evaluation</strong>: Batch processing for external services or GPU acceleration using
50   * {@link EAConfigurationBulkAsync}</li>
51   * <li><strong>Custom evaluation</strong>: User-provided {@link FitnessEvaluator} implementations</li>
52   * </ul>
53   * 
54   * <p>Common usage patterns:
55   * 
56   * <pre>{@code
57   * // Simple synchronous evaluation with default thread pool
58   * EAConfiguration<Double> config = EAConfigurationBuilder.<Double>builder()
59   * 		.chromosomeSpecs(chromosomeSpec)
60   * 		.parentSelectionPolicy(Tournament.of(3))
61   * 		.combinationPolicy(SinglePointCrossover.build())
62   * 		.mutationPolicies(List.of(RandomMutation.of(0.1)))
63   * 		.replacementStrategy(Elitism.builder().offspringRatio(0.8).build())
64   * 		.build();
65   * 
66   * EAExecutionContext<Double> context = EAExecutionContextBuilder.<Double>builder()
67   * 		.populationSize(100)
68   * 		.fitness(genotype -> computeFitness(genotype))
69   * 		.termination(Generations.of(100))
70   * 		.build();
71   * 
72   * EASystem<Double> eaSystem = EASystemFactory.from(config, context);
73   * 
74   * // Asynchronous evaluation for expensive fitness functions
75   * EAConfigurationBulkAsync<Double> asyncConfig = EAConfigurationBulkAsyncBuilder.<Double>builder()
76   * 		.chromosomeSpecs(chromosomeSpec)
77   * 		.parentSelectionPolicy(Tournament.of(3))
78   * 		.combinationPolicy(SinglePointCrossover.build())
79   * 		.mutationPolicies(List.of(RandomMutation.of(0.1)))
80   * 		.replacementStrategy(Elitism.builder().offspringRatio(0.8).build())
81   * 		.fitnessBulkAsync(genotypes -> evaluateBatch(genotypes))
82   * 		.build();
83   * 
84   * EASystem<Double> asyncSystem = EASystemFactory.from(asyncConfig, context);
85   * 
86   * // Custom evaluation strategy
87   * FitnessEvaluator<Double> customEvaluator = new CachedFitnessEvaluator<>(baseFitness, cacheSize);
88   * EASystem<Double> customSystem = EASystemFactory.from(config, context, executorService, customEvaluator);
89   * }</pre>
90   * 
91   * <p>Factory method selection guide:
92   * <ul>
93   * <li><strong>Fast fitness functions</strong>: Use synchronous methods with {@link EAConfiguration}</li>
94   * <li><strong>Expensive fitness functions</strong>: Use asynchronous methods with {@link EAConfigurationBulkAsync}</li>
95   * <li><strong>External fitness computation</strong>: Use custom evaluator methods</li>
96   * <li><strong>GPU acceleration</strong>: Use bulk async methods for batch processing</li>
97   * </ul>
98   * 
99   * <p>Thread pool considerations:
100  * <ul>
101  * <li><strong>Default behavior</strong>: Uses {@link ForkJoinPool#commonPool()} for automatic parallelization</li>
102  * <li><strong>Custom thread pools</strong>: Provide specific {@link ExecutorService} for resource control</li>
103  * <li><strong>Single-threaded</strong>: Use {@link java.util.concurrent.Executors#newSingleThreadExecutor()}</li>
104  * <li><strong>Resource management</strong>: Caller responsible for shutdown of custom thread pools</li>
105  * </ul>
106  * 
107  * @see EASystem
108  * @see EAConfiguration
109  * @see EAConfigurationBulkAsync
110  * @see EAExecutionContext
111  * @see FitnessEvaluator
112  */
113 public class EASystemFactory {
114 
115 	/**
116 	 * Prevents instantiation since it's a bunch of static methods
117 	 */
118 	private EASystemFactory() {
119 	}
120 
121 	/**
122 	 * Creates an {@link EASystem} with a custom fitness evaluator and explicit thread pool.
123 	 * 
124 	 * <p>This is the most flexible factory method that allows complete control over all components of the evolutionary
125 	 * algorithm system. It assembles and wires all necessary components including selection strategies, mutation
126 	 * operators, chromosome combinators, and replacement strategies.
127 	 * 
128 	 * <p>This method is primarily used internally by other factory methods, but can be used directly when you need to
129 	 * provide a custom {@link FitnessEvaluator} implementation such as cached, distributed, or specialized evaluation
130 	 * strategies.
131 	 * 
132 	 * @param <T>                the type of fitness values, must be comparable for selection operations
133 	 * @param eaConfiguration    the evolutionary algorithm configuration specifying genetic operators and strategies
134 	 * @param eaExecutionContext the execution context containing population parameters and fitness functions
135 	 * @param fitnessEvaluator   the fitness evaluator implementation to use for population evaluation
136 	 * @return a fully configured {@link EASystem} ready for evolution execution
137 	 * @throws IllegalArgumentException if any parameter is null
138 	 * @throws IllegalStateException    if no suitable replacement strategy handler can be found
139 	 */
140 	public static <T extends Comparable<T>> EASystem<T> from(final AbstractEAConfiguration<T> eaConfiguration,
141 			final AbstractEAExecutionContext<T> eaExecutionContext, final FitnessEvaluator<T> fitnessEvaluator) {
142 		Objects.requireNonNull(eaConfiguration);
143 		Objects.requireNonNull(eaExecutionContext);
144 		Objects.requireNonNull(fitnessEvaluator);
145 
146 		final var selectionPolicyHandlerResolver = new SelectionPolicyHandlerResolver<T>(eaExecutionContext);
147 
148 		final var parentSelectionPolicyHandler = selectionPolicyHandlerResolver
149 				.resolve(eaConfiguration.parentSelectionPolicy());
150 
151 		final var parentSelector = parentSelectionPolicyHandler.resolve(
152 				eaExecutionContext,
153 				eaConfiguration,
154 				selectionPolicyHandlerResolver,
155 				eaConfiguration.parentSelectionPolicy());
156 
157 		final var mutationPolicyHandlerResolver = new MutationPolicyHandlerResolver<T>(eaExecutionContext);
158 
159 		final var chromosomeCombinatorResolver = new ChromosomeCombinatorResolver<T>(eaExecutionContext);
160 
161 		final CombinationPolicy combinationPolicy = eaConfiguration.combinationPolicy();
162 		final List<ChromosomeCombinator<T>> chromosomeCombinators = eaConfiguration.chromosomeSpecs()
163 				.stream()
164 				.map(chromosome -> chromosomeCombinatorResolver.resolve(combinationPolicy, chromosome))
165 				.collect(Collectors.toList());
166 
167 		final List<Mutator> mutators = new ArrayList<>();
168 		final List<MutationPolicy> mutationPolicies = eaConfiguration.mutationPolicies();
169 		for (int i = 0; i < mutationPolicies.size(); i++) {
170 			final var mutationPolicy = mutationPolicies.get(i);
171 
172 			final var mutationPolicyHandler = mutationPolicyHandlerResolver.resolve(mutationPolicy);
173 			final var mutator = mutationPolicyHandler
174 					.createMutator(eaExecutionContext, eaConfiguration, mutationPolicyHandlerResolver, mutationPolicy);
175 
176 			mutators.add(mutator);
177 
178 		}
179 
180 		final var replacementStrategyHandlers = eaExecutionContext.replacementStrategyHandlers();
181 		final var replacementStrategy = eaConfiguration.replacementStrategy();
182 
183 		final Optional<ReplacementStrategyHandler<T>> replacementStrategyHandlerOpt = replacementStrategyHandlers.stream()
184 				.filter(replacementStrategyHandler -> replacementStrategyHandler.canHandle(replacementStrategy))
185 				.findFirst();
186 
187 		final ReplacementStrategyHandler<T> replacementStrategyHandler = replacementStrategyHandlerOpt.orElseThrow(
188 				() -> new IllegalStateException(
189 						"Could not find an implementation to handle the replacement strategy " + replacementStrategy));
190 		final var replacementStrategyImplementor = replacementStrategyHandler
191 				.resolve(eaExecutionContext, eaConfiguration, selectionPolicyHandlerResolver, replacementStrategy);
192 
193 		final long populationSize = eaExecutionContext.populationSize();
194 
195 		return new EASystem<>(eaConfiguration,
196 				populationSize,
197 				chromosomeCombinators,
198 				eaConfiguration.offspringGeneratedRatio(),
199 				parentSelector,
200 				mutators,
201 				replacementStrategyImplementor,
202 				eaExecutionContext,
203 				fitnessEvaluator);
204 	}
205 
206 	/**
207 	 * Creates an {@link EASystem} with synchronous fitness evaluation and explicit thread pool.
208 	 * 
209 	 * <p>This method is ideal for scenarios where fitness computation is relatively fast and can benefit from parallel
210 	 * evaluation across multiple threads. The fitness function defined in the configuration will be called synchronously
211 	 * for each individual or in parallel depending on the evaluator implementation.
212 	 * 
213 	 * <p>Use this method when:
214 	 * <ul>
215 	 * <li>Fitness computation is CPU-bound and relatively fast (&lt; 100ms per evaluation)</li>
216 	 * <li>You want control over the thread pool used for parallel evaluation</li>
217 	 * <li>You need deterministic thread pool behavior for testing or resource management</li>
218 	 * </ul>
219 	 * 
220 	 * @param <T>                 the type of fitness values, must be comparable for selection operations
221 	 * @param eaConfigurationSync the synchronous EA configuration with a simple fitness function
222 	 * @param eaExecutionContext  the execution context containing population and termination parameters
223 	 * @param executorService     the thread pool for parallel fitness evaluation (caller responsible for shutdown)
224 	 * @return a configured {@link EASystem} using synchronous fitness evaluation
225 	 * @throws IllegalArgumentException if any parameter is null
226 	 */
227 	public static <T extends Comparable<T>> EASystem<T> from(final EAConfiguration<T> eaConfigurationSync,
228 			final EAExecutionContext<T> eaExecutionContext, final ExecutorService executorService) {
229 
230 		final var fitnessEvaluator = new FitnessEvaluatorSync<>(eaExecutionContext, eaConfigurationSync, executorService);
231 		return from(eaConfigurationSync, eaExecutionContext, fitnessEvaluator);
232 	}
233 
234 	/**
235 	 * Creates an {@link EASystem} with synchronous fitness evaluation using the common thread pool.
236 	 * 
237 	 * <p>This is the most convenient method for creating evolutionary algorithms with simple fitness functions. It uses
238 	 * {@link ForkJoinPool#commonPool()} for automatic parallelization without requiring explicit thread pool management.
239 	 * 
240 	 * <p>This method is ideal for:
241 	 * <ul>
242 	 * <li>Quick prototyping and experimentation</li>
243 	 * <li>Applications where thread pool management is not critical</li>
244 	 * <li>Fast fitness functions that can benefit from automatic parallelization</li>
245 	 * <li>Educational purposes and simple examples</li>
246 	 * </ul>
247 	 * 
248 	 * @param <T>                 the type of fitness values, must be comparable for selection operations
249 	 * @param eaConfigurationSync the synchronous EA configuration with a simple fitness function
250 	 * @param eaExecutionContext  the execution context containing population and termination parameters
251 	 * @return a configured {@link EASystem} using synchronous fitness evaluation with common thread pool
252 	 * @throws IllegalArgumentException if any parameter is null
253 	 */
254 	public static <T extends Comparable<T>> EASystem<T> from(final EAConfiguration<T> eaConfigurationSync,
255 			final EAExecutionContext<T> eaExecutionContext) {
256 		final ExecutorService executorService = ForkJoinPool.commonPool();
257 		return from(eaConfigurationSync, eaExecutionContext, executorService);
258 	}
259 
260 	/**
261 	 * Creates an {@link EASystem} with bulk asynchronous fitness evaluation and explicit thread pool.
262 	 * 
263 	 * <p>This method is designed for expensive fitness functions that can benefit from batch processing or asynchronous
264 	 * evaluation. The bulk async evaluator can process entire populations at once, enabling optimization strategies like
265 	 * GPU acceleration, external service calls, or database batch operations.
266 	 * 
267 	 * <p>Use this method when:
268 	 * <ul>
269 	 * <li>Fitness computation involves external resources (databases, web services, files)</li>
270 	 * <li>Evaluation can be accelerated through batch processing (GPU, vectorized operations)</li>
271 	 * <li>Fitness computation is expensive (&gt; 100ms per evaluation)</li>
272 	 * <li>You need to optimize I/O operations through batching</li>
273 	 * </ul>
274 	 * 
275 	 * @param <T>                      the type of fitness values, must be comparable for selection operations
276 	 * @param eaConfigurationBulkAsync the bulk async EA configuration with batch fitness evaluation
277 	 * @param eaExecutionContext       the execution context containing population and termination parameters
278 	 * @param executorService          the thread pool for managing asynchronous operations (caller responsible for
279 	 *                                 shutdown)
280 	 * @return a configured {@link EASystem} using bulk asynchronous fitness evaluation
281 	 * @throws IllegalArgumentException if any parameter is null
282 	 */
283 	public static <T extends Comparable<T>> EASystem<T> from(final EAConfigurationBulkAsync<T> eaConfigurationBulkAsync,
284 			final EAExecutionContext<T> eaExecutionContext, final ExecutorService executorService) {
285 
286 		final var fitnessEvaluator = new FitnessEvaluatorBulkAsync<>(eaConfigurationBulkAsync, executorService);
287 		return from(eaConfigurationBulkAsync, eaExecutionContext, fitnessEvaluator);
288 	}
289 
290 	/**
291 	 * Creates an {@link EASystem} with bulk asynchronous fitness evaluation using the common thread pool.
292 	 * 
293 	 * <p>This convenience method provides the benefits of bulk asynchronous evaluation without requiring explicit thread
294 	 * pool management. It automatically uses {@link ForkJoinPool#commonPool()} for managing asynchronous operations.
295 	 * 
296 	 * <p>This method combines the convenience of automatic thread pool management with the power of bulk asynchronous
297 	 * evaluation, making it ideal for:
298 	 * <ul>
299 	 * <li>Expensive fitness functions in research and experimentation</li>
300 	 * <li>Applications requiring external resource access without complex thread management</li>
301 	 * <li>GPU-accelerated fitness evaluation with simple setup</li>
302 	 * <li>Prototype development with advanced evaluation strategies</li>
303 	 * </ul>
304 	 * 
305 	 * @param <T>                      the type of fitness values, must be comparable for selection operations
306 	 * @param eaConfigurationBulkAsync the bulk async EA configuration with batch fitness evaluation
307 	 * @param eaExecutionContext       the execution context containing population and termination parameters
308 	 * @return a configured {@link EASystem} using bulk asynchronous fitness evaluation with common thread pool
309 	 * @throws IllegalArgumentException if any parameter is null
310 	 */
311 	public static <T extends Comparable<T>> EASystem<T> from(final EAConfigurationBulkAsync<T> eaConfigurationBulkAsync,
312 			final EAExecutionContext<T> eaExecutionContext) {
313 		final ExecutorService executorService = ForkJoinPool.commonPool();
314 		return from(eaConfigurationBulkAsync, eaExecutionContext, executorService);
315 	}
316 
317 	/**
318 	 * Creates an {@link EASystem} with virtual thread-based fitness evaluation.
319 	 *
320 	 * <p>This method leverages virtual threads to provide massive parallelism for fitness evaluation without the
321 	 * overhead of traditional platform threads. Each genotype gets its own virtual thread, making this approach ideal
322 	 * for I/O-bound fitness functions and large populations.
323 	 *
324 	 * <p>Virtual threads excel in scenarios involving:
325 	 * <ul>
326 	 * <li><strong>I/O-bound fitness functions</strong>: Database queries, web service calls, file operations</li>
327 	 * <li><strong>Large populations</strong>: Thousands of individuals without thread pool limitations</li>
328 	 * <li><strong>Complex simulations</strong>: Long-running evaluations that benefit from massive parallelism</li>
329 	 * <li><strong>External service integration</strong>: Fitness evaluations requiring network calls</li>
330 	 * </ul>
331 	 *
332 	 * <p>Performance characteristics:
333 	 * <ul>
334 	 * <li><strong>Memory overhead</strong>: ~200 bytes per virtual thread vs ~2MB per platform thread</li>
335 	 * <li><strong>Creation cost</strong>: Nearly zero compared to platform threads</li>
336 	 * <li><strong>Blocking behavior</strong>: Virtual threads don't block carrier threads during I/O</li>
337 	 * <li><strong>Scalability</strong>: Can handle millions of concurrent virtual threads</li>
338 	 * </ul>
339 	 *
340 	 * <p><strong>Requirements:</strong> Java 25 or later
341 	 *
342 	 * @param <T>                 the type of fitness values, must be comparable for selection operations
343 	 * @param eaConfigurationSync the synchronous EA configuration with a simple fitness function
344 	 * @param eaExecutionContext  the execution context containing population and termination parameters
345 	 * @return a configured {@link EASystem} using virtual thread-based fitness evaluation
346 	 * @throws IllegalArgumentException      if any parameter is null
347 	 * @throws UnsupportedOperationException if virtual threads are not available in current JVM
348 	 */
349 	public static <T extends Comparable<T>> EASystem<T> fromWithVirtualThreads(
350 			final EAConfiguration<T> eaConfigurationSync, final EAExecutionContext<T> eaExecutionContext) {
351 
352 		final var fitnessEvaluator = new FitnessEvaluatorVirtualThread<>(eaExecutionContext, eaConfigurationSync);
353 		return from(eaConfigurationSync, eaExecutionContext, fitnessEvaluator);
354 	}
355 }