| 1 | package net.bmahe.genetics4j.core.evaluation; | |
| 2 | ||
| 3 | import java.util.ArrayList; | |
| 4 | import java.util.List; | |
| 5 | import java.util.Objects; | |
| 6 | import java.util.concurrent.CompletableFuture; | |
| 7 | import java.util.concurrent.ExecutorService; | |
| 8 | import java.util.concurrent.Executors; | |
| 9 | ||
| 10 | import org.apache.commons.lang3.Validate; | |
| 11 | import org.apache.logging.log4j.LogManager; | |
| 12 | import org.apache.logging.log4j.Logger; | |
| 13 | ||
| 14 | import net.bmahe.genetics4j.core.Fitness; | |
| 15 | import net.bmahe.genetics4j.core.Genotype; | |
| 16 | import net.bmahe.genetics4j.core.spec.EAConfiguration; | |
| 17 | import net.bmahe.genetics4j.core.spec.EAExecutionContext; | |
| 18 | ||
| 19 | /** | |
| 20 | * Virtual thread-based fitness evaluator that creates one virtual thread per individual evaluation. | |
| 21 | * | |
| 22 | * <p>This evaluator leverages Java 21+ virtual threads to provide massive parallelism for fitness evaluation without | |
| 23 | * the overhead of traditional platform threads. Each genotype gets its own virtual thread, making this approach | |
| 24 | * particularly suitable for: | |
| 25 | * | |
| 26 | * <ul> | |
| 27 | * <li><strong>I/O-bound fitness functions</strong>: Database queries, web service calls, file operations</li> | |
| 28 | * <li><strong>Large populations</strong>: Thousands of individuals without thread pool limitations</li> | |
| 29 | * <li><strong>Complex simulations</strong>: Long-running evaluations that benefit from massive parallelism</li> | |
| 30 | * <li><strong>External service integration</strong>: Fitness evaluations requiring network calls</li> | |
| 31 | * </ul> | |
| 32 | * | |
| 33 | * <p>Unlike traditional thread pool-based evaluators, this implementation: | |
| 34 | * <ul> | |
| 35 | * <li>Creates one virtual thread per genotype (no partitioning)</li> | |
| 36 | * <li>Leverages virtual thread's lightweight nature for maximum concurrency</li> | |
| 37 | * <li>Automatically manages virtual thread lifecycle</li> | |
| 38 | * <li>Provides optimal resource utilization for I/O-intensive workloads</li> | |
| 39 | * </ul> | |
| 40 | * | |
| 41 | * <p>Performance characteristics: | |
| 42 | * <ul> | |
| 43 | * <li><strong>Memory overhead</strong>: ~200 bytes per virtual thread vs ~2MB per platform thread</li> | |
| 44 | * <li><strong>Creation cost</strong>: Nearly zero compared to platform threads</li> | |
| 45 | * <li><strong>Blocking behavior</strong>: Virtual threads don't block carrier threads during I/O</li> | |
| 46 | * <li><strong>Scalability</strong>: Can handle millions of concurrent virtual threads</li> | |
| 47 | * </ul> | |
| 48 | * | |
| 49 | * <p>This evaluator automatically creates and manages a virtual thread executor, eliminating the need for explicit | |
| 50 | * thread pool configuration and management. | |
| 51 | * | |
| 52 | * @param <T> the type of fitness values produced, must be comparable for selection operations | |
| 53 | * @see FitnessEvaluator | |
| 54 | * @see FitnessEvaluatorSync | |
| 55 | * @see net.bmahe.genetics4j.core.Fitness | |
| 56 | */ | |
| 57 | public class FitnessEvaluatorVirtualThread<T extends Comparable<T>> implements FitnessEvaluator<T> { | |
| 58 | public static final Logger logger = LogManager.getLogger(FitnessEvaluatorVirtualThread.class); | |
| 59 | ||
| 60 | private final EAExecutionContext<T> eaExecutionContext; | |
| 61 | private final EAConfiguration<T> eaConfiguration; | |
| 62 | private final ExecutorService virtualThreadExecutor; | |
| 63 | ||
| 64 | public FitnessEvaluatorVirtualThread(final EAExecutionContext<T> _eaExecutionContext, | |
| 65 | final EAConfiguration<T> _eaConfiguration) { | |
| 66 | Objects.requireNonNull(_eaExecutionContext); | |
| 67 | Objects.requireNonNull(_eaConfiguration); | |
| 68 | ||
| 69 |
1
1. <init> : Removed assignment to member variable eaExecutionContext → SURVIVED |
this.eaExecutionContext = _eaExecutionContext; |
| 70 |
1
1. <init> : Removed assignment to member variable eaConfiguration → SURVIVED |
this.eaConfiguration = _eaConfiguration; |
| 71 |
2
1. <init> : Removed assignment to member variable virtualThreadExecutor → SURVIVED 2. <init> : removed call to java/util/concurrent/Executors::newVirtualThreadPerTaskExecutor → SURVIVED |
this.virtualThreadExecutor = Executors.newVirtualThreadPerTaskExecutor(); |
| 72 | } | |
| 73 | ||
| 74 | @Override | |
| 75 | public void preEvaluation() { | |
| 76 | logger.debug("Pre-evaluation setup for virtual thread fitness evaluator"); | |
| 77 | } | |
| 78 | ||
| 79 | @Override | |
| 80 | public void postEvaluation() { | |
| 81 | logger.debug("Post-evaluation cleanup for virtual thread fitness evaluator"); | |
| 82 | } | |
| 83 | ||
| 84 | @Override | |
| 85 | public List<T> evaluate(final long generation, final List<Genotype> population) { | |
| 86 | Validate.isTrue(generation >= 0); | |
| 87 | Objects.requireNonNull(population); | |
| 88 | ||
| 89 |
4
1. evaluate : removed conditional - replaced equality check with false → NO_COVERAGE 2. evaluate : negated conditional → NO_COVERAGE 3. evaluate : removed conditional - replaced equality check with true → NO_COVERAGE 4. evaluate : removed call to java/util/List::isEmpty → NO_COVERAGE |
if (population.isEmpty()) { |
| 90 |
2
1. evaluate : removed call to java/util/ArrayList::<init> → NO_COVERAGE 2. evaluate : replaced return value with Collections.emptyList for net/bmahe/genetics4j/core/evaluation/FitnessEvaluatorVirtualThread::evaluate → NO_COVERAGE |
return new ArrayList<>(); |
| 91 | } | |
| 92 | ||
| 93 | logger.debug("Evaluating {} individuals using virtual threads", population.size()); | |
| 94 | ||
| 95 |
1
1. evaluate : removed call to net/bmahe/genetics4j/core/spec/EAConfiguration::fitness → NO_COVERAGE |
final Fitness<T> fitness = eaConfiguration.fitness(); |
| 96 |
2
1. evaluate : removed call to java/util/ArrayList::<init> → NO_COVERAGE 2. evaluate : removed call to java/util/List::size → NO_COVERAGE |
final List<CompletableFuture<T>> evaluationTasks = new ArrayList<>(population.size()); |
| 97 | ||
| 98 | // Create one virtual thread task per genotype - no partitioning needed | |
| 99 | for (final Genotype genotype : population) { | |
| 100 |
1
1. evaluate : removed call to java/util/concurrent/CompletableFuture::supplyAsync → NO_COVERAGE |
final CompletableFuture<T> evaluationTask = CompletableFuture.supplyAsync(() -> { |
| 101 |
2
1. lambda$evaluate$0 : replaced return value with null for net/bmahe/genetics4j/core/evaluation/FitnessEvaluatorVirtualThread::lambda$evaluate$0 → NO_COVERAGE 2. lambda$evaluate$0 : removed call to net/bmahe/genetics4j/core/Fitness::compute → NO_COVERAGE |
return fitness.compute(genotype); |
| 102 | }, virtualThreadExecutor); | |
| 103 | ||
| 104 |
1
1. evaluate : removed call to java/util/List::add → NO_COVERAGE |
evaluationTasks.add(evaluationTask); |
| 105 | } | |
| 106 | ||
| 107 | // Wait for all evaluations to complete and collect results in order | |
| 108 |
2
1. evaluate : removed call to java/util/ArrayList::<init> → NO_COVERAGE 2. evaluate : removed call to java/util/List::size → NO_COVERAGE |
final List<T> fitnessScores = new ArrayList<>(population.size()); |
| 109 | for (final CompletableFuture<T> task : evaluationTasks) { | |
| 110 |
2
1. evaluate : removed call to java/util/List::add → NO_COVERAGE 2. evaluate : removed call to java/util/concurrent/CompletableFuture::join → NO_COVERAGE |
fitnessScores.add(task.join()); |
| 111 | } | |
| 112 | ||
| 113 | logger.debug("Completed evaluation of {} individuals", population.size()); | |
| 114 |
1
1. evaluate : replaced return value with Collections.emptyList for net/bmahe/genetics4j/core/evaluation/FitnessEvaluatorVirtualThread::evaluate → NO_COVERAGE |
return fitnessScores; |
| 115 | } | |
| 116 | } | |
Mutations | ||
| 69 |
1.1 |
|
| 70 |
1.1 |
|
| 71 |
1.1 2.2 |
|
| 89 |
1.1 2.2 3.3 4.4 |
|
| 90 |
1.1 2.2 |
|
| 95 |
1.1 |
|
| 96 |
1.1 2.2 |
|
| 100 |
1.1 |
|
| 101 |
1.1 2.2 |
|
| 104 |
1.1 |
|
| 108 |
1.1 2.2 |
|
| 110 |
1.1 2.2 |
|
| 114 |
1.1 |