EASystemFactory.java
package net.bmahe.genetics4j.core;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ForkJoinPool;
import java.util.stream.Collectors;
import net.bmahe.genetics4j.core.combination.ChromosomeCombinator;
import net.bmahe.genetics4j.core.combination.ChromosomeCombinatorResolver;
import net.bmahe.genetics4j.core.evaluation.FitnessEvaluator;
import net.bmahe.genetics4j.core.evaluation.FitnessEvaluatorBulkAsync;
import net.bmahe.genetics4j.core.evaluation.FitnessEvaluatorSync;
import net.bmahe.genetics4j.core.evaluation.FitnessEvaluatorVirtualThread;
import net.bmahe.genetics4j.core.mutation.MutationPolicyHandlerResolver;
import net.bmahe.genetics4j.core.mutation.Mutator;
import net.bmahe.genetics4j.core.replacement.ReplacementStrategyHandler;
import net.bmahe.genetics4j.core.selection.SelectionPolicyHandlerResolver;
import net.bmahe.genetics4j.core.spec.AbstractEAConfiguration;
import net.bmahe.genetics4j.core.spec.AbstractEAExecutionContext;
import net.bmahe.genetics4j.core.spec.EAConfiguration;
import net.bmahe.genetics4j.core.spec.EAConfigurationBulkAsync;
import net.bmahe.genetics4j.core.spec.EAExecutionContext;
import net.bmahe.genetics4j.core.spec.combination.CombinationPolicy;
import net.bmahe.genetics4j.core.spec.mutation.MutationPolicy;
/**
* Factory class providing convenient methods for creating properly configured {@link EASystem} instances.
*
* <p>EASystemFactory simplifies the complex process of assembling all the components required for evolutionary
* algorithms by providing pre-configured factory methods that handle the initialization and wiring of selection
* strategies, mutation operators, combination policies, and fitness evaluators.
*
* <p>The factory abstracts away the complexity of manually creating and configuring:
* <ul>
* <li><strong>Selection policy resolvers</strong>: Components that match selection strategies to implementations</li>
* <li><strong>Mutation policy resolvers</strong>: Components that handle different mutation strategies</li>
* <li><strong>Chromosome combinators</strong>: Components responsible for crossover operations</li>
* <li><strong>Replacement strategy handlers</strong>: Components managing population replacement</li>
* <li><strong>Fitness evaluators</strong>: Components handling fitness computation strategies</li>
* </ul>
*
* <p>Factory methods are organized by evaluation strategy:
* <ul>
* <li><strong>Synchronous evaluation</strong>: Traditional single-threaded or parallel evaluation using
* {@link EAConfiguration}</li>
* <li><strong>Bulk asynchronous evaluation</strong>: Batch processing for external services or GPU acceleration using
* {@link EAConfigurationBulkAsync}</li>
* <li><strong>Custom evaluation</strong>: User-provided {@link FitnessEvaluator} implementations</li>
* </ul>
*
* <p>Common usage patterns:
*
* <pre>{@code
* // Simple synchronous evaluation with default thread pool
* EAConfiguration<Double> config = EAConfigurationBuilder.<Double>builder()
* .chromosomeSpecs(chromosomeSpec)
* .parentSelectionPolicy(Tournament.of(3))
* .combinationPolicy(SinglePointCrossover.build())
* .mutationPolicies(List.of(RandomMutation.of(0.1)))
* .replacementStrategy(Elitism.builder()
* .offspringRatio(0.8)
* .build())
* .build();
*
* EAExecutionContext<Double> context = EAExecutionContextBuilder.<Double>builder()
* .populationSize(100)
* .fitness(genotype -> computeFitness(genotype))
* .termination(Generations.of(100))
* .build();
*
* EASystem<Double> eaSystem = EASystemFactory.from(config, context);
*
* // Asynchronous evaluation for expensive fitness functions
* EAConfigurationBulkAsync<Double> asyncConfig = EAConfigurationBulkAsyncBuilder.<Double>builder()
* .chromosomeSpecs(chromosomeSpec)
* .parentSelectionPolicy(Tournament.of(3))
* .combinationPolicy(SinglePointCrossover.build())
* .mutationPolicies(List.of(RandomMutation.of(0.1)))
* .replacementStrategy(Elitism.builder()
* .offspringRatio(0.8)
* .build())
* .fitnessBulkAsync(genotypes -> evaluateBatch(genotypes))
* .build();
*
* EASystem<Double> asyncSystem = EASystemFactory.from(asyncConfig, context);
*
* // Custom evaluation strategy
* FitnessEvaluator<Double> customEvaluator = new CachedFitnessEvaluator<>(baseFitness, cacheSize);
* EASystem<Double> customSystem = EASystemFactory.from(config, context, executorService, customEvaluator);
* }</pre>
*
* <p>Factory method selection guide:
* <ul>
* <li><strong>Fast fitness functions</strong>: Use synchronous methods with {@link EAConfiguration}</li>
* <li><strong>Expensive fitness functions</strong>: Use asynchronous methods with {@link EAConfigurationBulkAsync}</li>
* <li><strong>External fitness computation</strong>: Use custom evaluator methods</li>
* <li><strong>GPU acceleration</strong>: Use bulk async methods for batch processing</li>
* </ul>
*
* <p>Thread pool considerations:
* <ul>
* <li><strong>Default behavior</strong>: Uses {@link ForkJoinPool#commonPool()} for automatic parallelization</li>
* <li><strong>Custom thread pools</strong>: Provide specific {@link ExecutorService} for resource control</li>
* <li><strong>Single-threaded</strong>: Use {@link java.util.concurrent.Executors#newSingleThreadExecutor()}</li>
* <li><strong>Resource management</strong>: Caller responsible for shutdown of custom thread pools</li>
* </ul>
*
* @see EASystem
* @see EAConfiguration
* @see EAConfigurationBulkAsync
* @see EAExecutionContext
* @see FitnessEvaluator
*/
public class EASystemFactory {
/**
* Prevents instantiation since it's a bunch of static methods
*/
private EASystemFactory() {
}
/**
* Creates an {@link EASystem} with a custom fitness evaluator and explicit thread pool.
*
* <p>This is the most flexible factory method that allows complete control over all components of the evolutionary
* algorithm system. It assembles and wires all necessary components including selection strategies, mutation
* operators, chromosome combinators, and replacement strategies.
*
* <p>This method is primarily used internally by other factory methods, but can be used directly when you need to
* provide a custom {@link FitnessEvaluator} implementation such as cached, distributed, or specialized evaluation
* strategies.
*
* @param <T> the type of fitness values, must be comparable for selection operations
* @param eaConfiguration the evolutionary algorithm configuration specifying genetic operators and strategies
* @param eaExecutionContext the execution context containing population parameters and fitness functions
* @param fitnessEvaluator the fitness evaluator implementation to use for population evaluation
* @return a fully configured {@link EASystem} ready for evolution execution
* @throws IllegalArgumentException if any parameter is null
* @throws IllegalStateException if no suitable replacement strategy handler can be found
*/
public static <T extends Comparable<T>> EASystem<T> from(final AbstractEAConfiguration<T> eaConfiguration,
final AbstractEAExecutionContext<T> eaExecutionContext, final FitnessEvaluator<T> fitnessEvaluator) {
Objects.requireNonNull(eaConfiguration);
Objects.requireNonNull(eaExecutionContext);
Objects.requireNonNull(fitnessEvaluator);
final var selectionPolicyHandlerResolver = new SelectionPolicyHandlerResolver<T>(eaExecutionContext);
final var parentSelectionPolicyHandler = selectionPolicyHandlerResolver
.resolve(eaConfiguration.parentSelectionPolicy());
final var parentSelector = parentSelectionPolicyHandler.resolve(eaExecutionContext,
eaConfiguration,
selectionPolicyHandlerResolver,
eaConfiguration.parentSelectionPolicy());
final var mutationPolicyHandlerResolver = new MutationPolicyHandlerResolver<T>(eaExecutionContext);
final var chromosomeCombinatorResolver = new ChromosomeCombinatorResolver<T>(eaExecutionContext);
final CombinationPolicy combinationPolicy = eaConfiguration.combinationPolicy();
final List<ChromosomeCombinator<T>> chromosomeCombinators = eaConfiguration.chromosomeSpecs()
.stream()
.map((chromosome) -> {
return chromosomeCombinatorResolver.resolve(combinationPolicy, chromosome);
})
.collect(Collectors.toList());
final List<Mutator> mutators = new ArrayList<>();
final List<MutationPolicy> mutationPolicies = eaConfiguration.mutationPolicies();
for (int i = 0; i < mutationPolicies.size(); i++) {
final var mutationPolicy = mutationPolicies.get(i);
final var mutationPolicyHandler = mutationPolicyHandlerResolver.resolve(mutationPolicy);
final var mutator = mutationPolicyHandler
.createMutator(eaExecutionContext, eaConfiguration, mutationPolicyHandlerResolver, mutationPolicy);
mutators.add(mutator);
}
final var replacementStrategyHandlers = eaExecutionContext.replacementStrategyHandlers();
final var replacementStrategy = eaConfiguration.replacementStrategy();
final Optional<ReplacementStrategyHandler<T>> replacementStrategyHandlerOpt = replacementStrategyHandlers.stream()
.filter(replacementStrategyHandler -> replacementStrategyHandler.canHandle(replacementStrategy))
.findFirst();
final ReplacementStrategyHandler<T> replacementStrategyHandler = replacementStrategyHandlerOpt
.orElseThrow(() -> new IllegalStateException(
"Could not find an implementation to handle the replacement strategy " + replacementStrategy));
final var replacementStrategyImplementor = replacementStrategyHandler
.resolve(eaExecutionContext, eaConfiguration, selectionPolicyHandlerResolver, replacementStrategy);
final long populationSize = eaExecutionContext.populationSize();
return new EASystem<>(eaConfiguration,
populationSize,
chromosomeCombinators,
eaConfiguration.offspringGeneratedRatio(),
parentSelector,
mutators,
replacementStrategyImplementor,
eaExecutionContext,
fitnessEvaluator);
}
/**
* Creates an {@link EASystem} with synchronous fitness evaluation and explicit thread pool.
*
* <p>This method is ideal for scenarios where fitness computation is relatively fast and can benefit from parallel
* evaluation across multiple threads. The fitness function defined in the configuration will be called synchronously
* for each individual or in parallel depending on the evaluator implementation.
*
* <p>Use this method when:
* <ul>
* <li>Fitness computation is CPU-bound and relatively fast (< 100ms per evaluation)</li>
* <li>You want control over the thread pool used for parallel evaluation</li>
* <li>You need deterministic thread pool behavior for testing or resource management</li>
* </ul>
*
* @param <T> the type of fitness values, must be comparable for selection operations
* @param eaConfigurationSync the synchronous EA configuration with a simple fitness function
* @param eaExecutionContext the execution context containing population and termination parameters
* @param executorService the thread pool for parallel fitness evaluation (caller responsible for shutdown)
* @return a configured {@link EASystem} using synchronous fitness evaluation
* @throws IllegalArgumentException if any parameter is null
*/
public static <T extends Comparable<T>> EASystem<T> from(final EAConfiguration<T> eaConfigurationSync,
final EAExecutionContext<T> eaExecutionContext, final ExecutorService executorService) {
final var fitnessEvaluator = new FitnessEvaluatorSync<>(eaExecutionContext, eaConfigurationSync, executorService);
return from(eaConfigurationSync, eaExecutionContext, fitnessEvaluator);
}
/**
* Creates an {@link EASystem} with synchronous fitness evaluation using the common thread pool.
*
* <p>This is the most convenient method for creating evolutionary algorithms with simple fitness functions. It uses
* {@link ForkJoinPool#commonPool()} for automatic parallelization without requiring explicit thread pool management.
*
* <p>This method is ideal for:
* <ul>
* <li>Quick prototyping and experimentation</li>
* <li>Applications where thread pool management is not critical</li>
* <li>Fast fitness functions that can benefit from automatic parallelization</li>
* <li>Educational purposes and simple examples</li>
* </ul>
*
* @param <T> the type of fitness values, must be comparable for selection operations
* @param eaConfigurationSync the synchronous EA configuration with a simple fitness function
* @param eaExecutionContext the execution context containing population and termination parameters
* @return a configured {@link EASystem} using synchronous fitness evaluation with common thread pool
* @throws IllegalArgumentException if any parameter is null
*/
public static <T extends Comparable<T>> EASystem<T> from(final EAConfiguration<T> eaConfigurationSync,
final EAExecutionContext<T> eaExecutionContext) {
final ExecutorService executorService = ForkJoinPool.commonPool();
return from(eaConfigurationSync, eaExecutionContext, executorService);
}
/**
* Creates an {@link EASystem} with bulk asynchronous fitness evaluation and explicit thread pool.
*
* <p>This method is designed for expensive fitness functions that can benefit from batch processing or asynchronous
* evaluation. The bulk async evaluator can process entire populations at once, enabling optimization strategies like
* GPU acceleration, external service calls, or database batch operations.
*
* <p>Use this method when:
* <ul>
* <li>Fitness computation involves external resources (databases, web services, files)</li>
* <li>Evaluation can be accelerated through batch processing (GPU, vectorized operations)</li>
* <li>Fitness computation is expensive (> 100ms per evaluation)</li>
* <li>You need to optimize I/O operations through batching</li>
* </ul>
*
* @param <T> the type of fitness values, must be comparable for selection operations
* @param eaConfigurationBulkAsync the bulk async EA configuration with batch fitness evaluation
* @param eaExecutionContext the execution context containing population and termination parameters
* @param executorService the thread pool for managing asynchronous operations (caller responsible for
* shutdown)
* @return a configured {@link EASystem} using bulk asynchronous fitness evaluation
* @throws IllegalArgumentException if any parameter is null
*/
public static <T extends Comparable<T>> EASystem<T> from(final EAConfigurationBulkAsync<T> eaConfigurationBulkAsync,
final EAExecutionContext<T> eaExecutionContext, final ExecutorService executorService) {
final var fitnessEvaluator = new FitnessEvaluatorBulkAsync<>(eaConfigurationBulkAsync, executorService);
return from(eaConfigurationBulkAsync, eaExecutionContext, fitnessEvaluator);
}
/**
* Creates an {@link EASystem} with bulk asynchronous fitness evaluation using the common thread pool.
*
* <p>This convenience method provides the benefits of bulk asynchronous evaluation without requiring explicit thread
* pool management. It automatically uses {@link ForkJoinPool#commonPool()} for managing asynchronous operations.
*
* <p>This method combines the convenience of automatic thread pool management with the power of bulk asynchronous
* evaluation, making it ideal for:
* <ul>
* <li>Expensive fitness functions in research and experimentation</li>
* <li>Applications requiring external resource access without complex thread management</li>
* <li>GPU-accelerated fitness evaluation with simple setup</li>
* <li>Prototype development with advanced evaluation strategies</li>
* </ul>
*
* @param <T> the type of fitness values, must be comparable for selection operations
* @param eaConfigurationBulkAsync the bulk async EA configuration with batch fitness evaluation
* @param eaExecutionContext the execution context containing population and termination parameters
* @return a configured {@link EASystem} using bulk asynchronous fitness evaluation with common thread pool
* @throws IllegalArgumentException if any parameter is null
*/
public static <T extends Comparable<T>> EASystem<T> from(final EAConfigurationBulkAsync<T> eaConfigurationBulkAsync,
final EAExecutionContext<T> eaExecutionContext) {
final ExecutorService executorService = ForkJoinPool.commonPool();
return from(eaConfigurationBulkAsync, eaExecutionContext, executorService);
}
/**
* Creates an {@link EASystem} with virtual thread-based fitness evaluation.
*
* <p>This method leverages Java 21+ virtual threads to provide massive parallelism for fitness evaluation without
* the overhead of traditional platform threads. Each genotype gets its own virtual thread, making this approach
* ideal for I/O-bound fitness functions and large populations.
*
* <p>Virtual threads excel in scenarios involving:
* <ul>
* <li><strong>I/O-bound fitness functions</strong>: Database queries, web service calls, file operations</li>
* <li><strong>Large populations</strong>: Thousands of individuals without thread pool limitations</li>
* <li><strong>Complex simulations</strong>: Long-running evaluations that benefit from massive parallelism</li>
* <li><strong>External service integration</strong>: Fitness evaluations requiring network calls</li>
* </ul>
*
* <p>Performance characteristics:
* <ul>
* <li><strong>Memory overhead</strong>: ~200 bytes per virtual thread vs ~2MB per platform thread</li>
* <li><strong>Creation cost</strong>: Nearly zero compared to platform threads</li>
* <li><strong>Blocking behavior</strong>: Virtual threads don't block carrier threads during I/O</li>
* <li><strong>Scalability</strong>: Can handle millions of concurrent virtual threads</li>
* </ul>
*
* <p><strong>Requirements:</strong> Java 21+ with virtual threads enabled
*
* @param <T> the type of fitness values, must be comparable for selection operations
* @param eaConfigurationSync the synchronous EA configuration with a simple fitness function
* @param eaExecutionContext the execution context containing population and termination parameters
* @return a configured {@link EASystem} using virtual thread-based fitness evaluation
* @throws IllegalArgumentException if any parameter is null
* @throws UnsupportedOperationException if virtual threads are not available in current JVM
*/
public static <T extends Comparable<T>> EASystem<T> fromWithVirtualThreads(
final EAConfiguration<T> eaConfigurationSync, final EAExecutionContext<T> eaExecutionContext) {
final var fitnessEvaluator = new FitnessEvaluatorVirtualThread<>(eaExecutionContext, eaConfigurationSync);
return from(eaConfigurationSync, eaExecutionContext, fitnessEvaluator);
}
}