FitnessEvaluatorVirtualThread.java

package net.bmahe.genetics4j.core.evaluation;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import org.apache.commons.lang3.Validate;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import net.bmahe.genetics4j.core.Fitness;
import net.bmahe.genetics4j.core.Genotype;
import net.bmahe.genetics4j.core.spec.EAConfiguration;
import net.bmahe.genetics4j.core.spec.EAExecutionContext;

/**
 * Virtual thread-based fitness evaluator that creates one virtual thread per individual evaluation.
 *
 * <p>This evaluator 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
 * particularly suitable for:
 *
 * <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>Unlike traditional thread pool-based evaluators, this implementation:
 * <ul>
 * <li>Creates one virtual thread per genotype (no partitioning)</li>
 * <li>Leverages virtual thread's lightweight nature for maximum concurrency</li>
 * <li>Automatically manages virtual thread lifecycle</li>
 * <li>Provides optimal resource utilization for I/O-intensive workloads</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>This evaluator automatically creates and manages a virtual thread executor, eliminating the need for explicit
 * thread pool configuration and management.
 *
 * @param <T> the type of fitness values produced, must be comparable for selection operations
 * @see FitnessEvaluator
 * @see FitnessEvaluatorSync
 * @see net.bmahe.genetics4j.core.Fitness
 */
public class FitnessEvaluatorVirtualThread<T extends Comparable<T>> implements FitnessEvaluator<T> {
	public static final Logger logger = LogManager.getLogger(FitnessEvaluatorVirtualThread.class);

	private final EAExecutionContext<T> eaExecutionContext;
	private final EAConfiguration<T> eaConfiguration;
	private final ExecutorService virtualThreadExecutor;

	public FitnessEvaluatorVirtualThread(final EAExecutionContext<T> _eaExecutionContext,
			final EAConfiguration<T> _eaConfiguration) {
		Objects.requireNonNull(_eaExecutionContext);
		Objects.requireNonNull(_eaConfiguration);

		this.eaExecutionContext = _eaExecutionContext;
		this.eaConfiguration = _eaConfiguration;
		this.virtualThreadExecutor = Executors.newVirtualThreadPerTaskExecutor();
	}

	@Override
	public void preEvaluation() {
		logger.debug("Pre-evaluation setup for virtual thread fitness evaluator");
	}

	@Override
	public void postEvaluation() {
		logger.debug("Post-evaluation cleanup for virtual thread fitness evaluator");
	}

	@Override
	public List<T> evaluate(final long generation, final List<Genotype> population) {
		Validate.isTrue(generation >= 0);
		Objects.requireNonNull(population);

		if (population.isEmpty()) {
			return new ArrayList<>();
		}

		logger.debug("Evaluating {} individuals using virtual threads", population.size());

		final Fitness<T> fitness = eaConfiguration.fitness();
		final List<CompletableFuture<T>> evaluationTasks = new ArrayList<>(population.size());

		// Create one virtual thread task per genotype - no partitioning needed
		for (final Genotype genotype : population) {
			final CompletableFuture<T> evaluationTask = CompletableFuture.supplyAsync(() -> {
				return fitness.compute(genotype);
			}, virtualThreadExecutor);

			evaluationTasks.add(evaluationTask);
		}

		// Wait for all evaluations to complete and collect results in order
		final List<T> fitnessScores = new ArrayList<>(population.size());
		for (final CompletableFuture<T> task : evaluationTasks) {
			fitnessScores.add(task.join());
		}

		logger.debug("Completed evaluation of {} individuals", population.size());
		return fitnessScores;
	}
}