1 package net.bmahe.genetics4j.gpu.spec.fitness;
2
3 import java.util.List;
4 import java.util.concurrent.CompletableFuture;
5 import java.util.concurrent.ExecutorService;
6
7 import org.apache.logging.log4j.LogManager;
8 import org.apache.logging.log4j.Logger;
9
10 import net.bmahe.genetics4j.core.Genotype;
11 import net.bmahe.genetics4j.gpu.opencl.OpenCLExecutionContext;
12
13 /**
14 * Abstract base class for implementing OpenCL-based fitness evaluation in GPU-accelerated evolutionary algorithms.
15 *
16 * <p>OpenCLFitness provides the framework for evaluating population fitness using OpenCL kernels executed on GPU
17 * devices. This class defines the lifecycle and coordination patterns needed for efficient GPU-based fitness
18 * computation, including resource management, data transfer, and kernel execution orchestration.
19 *
20 * <p>The fitness evaluation lifecycle consists of several phases:
21 * <ol>
22 * <li><strong>Global initialization</strong>: One-time setup before any evaluations
23 * ({@link #beforeAllEvaluations})</li>
24 * <li><strong>Per-device initialization</strong>: Setup for each OpenCL device context</li>
25 * <li><strong>Generation setup</strong>: Preparation before each generation evaluation</li>
26 * <li><strong>Computation</strong>: Actual fitness evaluation using OpenCL kernels</li>
27 * <li><strong>Generation cleanup</strong>: Cleanup after each generation evaluation</li>
28 * <li><strong>Per-device cleanup</strong>: Cleanup for each OpenCL device context</li>
29 * <li><strong>Global cleanup</strong>: Final cleanup after all evaluations ({@link #afterAllEvaluations})</li>
30 * </ol>
31 *
32 * <p>Key responsibilities for implementations:
33 * <ul>
34 * <li><strong>Data preparation</strong>: Convert genotypes to GPU-compatible data formats</li>
35 * <li><strong>Memory management</strong>: Allocate and manage GPU memory buffers</li>
36 * <li><strong>Kernel execution</strong>: Configure and execute OpenCL kernels with appropriate parameters</li>
37 * <li><strong>Result extraction</strong>: Retrieve and convert fitness values from GPU memory</li>
38 * <li><strong>Resource cleanup</strong>: Ensure proper cleanup of GPU resources</li>
39 * </ul>
40 *
41 * <p>Common implementation patterns:
42 *
43 * <pre>
44 * {
45 * @code
46 * public class MyGPUFitness extends OpenCLFitness<Double> {
47 *
48 * private CLData inputBuffer;
49 * private CLData outputBuffer;
50 *
51 * @Override
52 * public void beforeAllEvaluations(OpenCLExecutionContext context, ExecutorService executor) {
53 * // Allocate GPU memory buffers that persist across generations
54 * int maxPopulationSize = getMaxPopulationSize();
55 * inputBuffer = CLData.allocateFloat(context, maxPopulationSize * chromosomeSize);
56 * outputBuffer = CLData.allocateFloat(context, maxPopulationSize);
57 * }
58 *
59 * @Override
60 * public CompletableFuture<List<Double>> compute(OpenCLExecutionContext context, ExecutorService executor,
61 * long generation, List<Genotype> genotypes) {
62 *
63 * return CompletableFuture.supplyAsync(() -> {
64 * // Transfer genotype data to GPU
65 * transferGenotypesToGPU(context, genotypes, inputBuffer);
66 *
67 * // Execute fitness evaluation kernel
68 * executeKernel(context, "fitness_kernel", genotypes.size());
69 *
70 * // Retrieve results from GPU
71 * return extractFitnessValues(context, outputBuffer, genotypes.size());
72 * }, executor);
73 * }
74 *
75 * @Override
76 * public void afterAllEvaluations(OpenCLExecutionContext context, ExecutorService executor) {
77 * // Clean up GPU memory
78 * inputBuffer.release();
79 * outputBuffer.release();
80 * }
81 * }
82 * }
83 * </pre>
84 *
85 * <p>Performance optimization strategies:
86 * <ul>
87 * <li><strong>Memory reuse</strong>: Allocate buffers once in {@link #beforeAllEvaluations} and reuse across
88 * generations</li>
89 * <li><strong>Asynchronous execution</strong>: Use CompletableFuture for non-blocking GPU operations</li>
90 * <li><strong>Batch processing</strong>: Process entire populations in single kernel launches</li>
91 * <li><strong>Memory coalescing</strong>: Organize data layouts for optimal GPU memory access patterns</li>
92 * <li><strong>Kernel optimization</strong>: Design kernels to maximize GPU utilization and minimize divergence</li>
93 * </ul>
94 *
95 * <p>Error handling and robustness:
96 * <ul>
97 * <li><strong>GPU errors</strong>: Handle OpenCL errors gracefully and provide meaningful error messages</li>
98 * <li><strong>Memory management</strong>: Ensure proper cleanup even in exceptional circumstances</li>
99 * <li><strong>Device failures</strong>: Support graceful degradation when GPU devices fail</li>
100 * <li><strong>Timeout handling</strong>: Implement appropriate timeouts for long-running kernels</li>
101 * </ul>
102 *
103 * <p>Multi-device considerations:
104 * <ul>
105 * <li><strong>Device-specific setup</strong>: Separate contexts and buffers for each device</li>
106 * <li><strong>Load balancing</strong>: Coordinate with the framework's automatic population partitioning</li>
107 * <li><strong>Resource isolation</strong>: Ensure proper isolation of resources between devices</li>
108 * <li><strong>Synchronization</strong>: Coordinate results from multiple devices</li>
109 * </ul>
110 *
111 * @param <T> the type of fitness values produced, must be comparable for selection operations
112 * @see net.bmahe.genetics4j.gpu.GPUFitnessEvaluator
113 * @see OpenCLExecutionContext
114 * @see net.bmahe.genetics4j.gpu.opencl.model.CLData
115 */
116 public abstract class OpenCLFitness<T extends Comparable<T>> {
117 public static final Logger logger = LogManager.getLogger(OpenCLFitness.class);
118
119 /**
120 * Global initialization hook called once before any fitness evaluations begin.
121 *
122 * <p>This method is called once at the beginning of the evolutionary algorithm execution, before any OpenCL contexts
123 * are created or evaluations are performed. Use this method for global initialization that applies to all devices
124 * and generations.
125 *
126 * <p>Typical use cases:
127 * <ul>
128 * <li>Initialize problem-specific constants or parameters</li>
129 * <li>Load reference data or configuration</li>
130 * <li>Set up logging or monitoring infrastructure</li>
131 * <li>Validate problem constraints or requirements</li>
132 * </ul>
133 *
134 * <p>This method is called on the main thread before any concurrent operations begin.
135 *
136 * @see #beforeAllEvaluations(OpenCLExecutionContext, ExecutorService)
137 */
138 public void beforeAllEvaluations() {
139 }
140
141 /**
142 * Per-device initialization hook called for each OpenCL execution context.
143 *
144 * <p>This method is called once for each OpenCL device that will be used for fitness evaluation. It allows
145 * device-specific initialization such as memory allocation, buffer creation, and device-specific resource setup.
146 *
147 * <p>Typical use cases:
148 * <ul>
149 * <li>Allocate GPU memory buffers that persist across generations</li>
150 * <li>Pre-load static data to GPU memory</li>
151 * <li>Initialize device-specific data structures</li>
152 * <li>Set up device-specific kernels or configurations</li>
153 * </ul>
154 *
155 * <p>Memory allocated in this method should typically be released in the corresponding
156 * {@link #afterAllEvaluations(OpenCLExecutionContext, ExecutorService)} method.
157 *
158 * @param openCLExecutionContext the OpenCL execution context for a specific device
159 * @param executorService the executor service for asynchronous operations
160 * @see #afterAllEvaluations(OpenCLExecutionContext, ExecutorService)
161 */
162 public void beforeAllEvaluations(final OpenCLExecutionContext openCLExecutionContext,
163 final ExecutorService executorService) {
164 }
165
166 /**
167 * Global preparation hook called before each generation evaluation.
168 *
169 * <p>This method is called before fitness evaluation of each generation, providing an opportunity for global
170 * preparation that applies across all devices. It receives the generation number and complete population for
171 * context.
172 *
173 * <p>Typical use cases:
174 * <ul>
175 * <li>Update generation-specific parameters or configurations</li>
176 * <li>Log generation start or population statistics</li>
177 * <li>Prepare global data structures for the upcoming evaluation</li>
178 * <li>Implement adaptive behavior based on generation number</li>
179 * </ul>
180 *
181 * @param generation the current generation number (0-based)
182 * @param genotypes the complete population to be evaluated
183 * @see #beforeEvaluation(OpenCLExecutionContext, ExecutorService, long, List)
184 */
185 public void beforeEvaluation(final long generation, final List<Genotype> genotypes) {
186 }
187
188 /**
189 * Per-device preparation hook called before each device partition evaluation.
190 *
191 * <p>This method is called for each device before evaluating its assigned partition of the population. It provides
192 * access to the device context and the specific genotypes that will be evaluated on this device.
193 *
194 * <p>Typical use cases:
195 * <ul>
196 * <li>Transfer genotype data to device memory</li>
197 * <li>Update device-specific parameters for this generation</li>
198 * <li>Prepare input buffers with population data</li>
199 * <li>Set up kernel arguments that vary by generation</li>
200 * </ul>
201 *
202 * @param openCLExecutionContext the OpenCL execution context for this device
203 * @param executorService the executor service for asynchronous operations
204 * @param generation the current generation number (0-based)
205 * @param genotypes the partition of genotypes to be evaluated on this device
206 * @see #afterEvaluation(OpenCLExecutionContext, ExecutorService, long, List)
207 */
208 public void beforeEvaluation(final OpenCLExecutionContext openCLExecutionContext,
209 final ExecutorService executorService, final long generation, final List<Genotype> genotypes) {
210 }
211
212 /**
213 * Performs the actual fitness computation using OpenCL kernels on the GPU.
214 *
215 * <p>This is the core method that implements GPU-based fitness evaluation. It receives a partition of the population
216 * and must return corresponding fitness values using OpenCL kernel execution on the specified device.
217 *
218 * <p>Implementation requirements:
219 * <ul>
220 * <li><strong>Return order</strong>: Fitness values must correspond to genotypes in the same order</li>
221 * <li><strong>Size consistency</strong>: Return exactly one fitness value per input genotype</li>
222 * <li><strong>Asynchronous execution</strong>: Use the executor service for non-blocking GPU operations</li>
223 * <li><strong>Error handling</strong>: Handle GPU errors gracefully and provide meaningful exceptions</li>
224 * </ul>
225 *
226 * <p>Common implementation pattern:
227 * <ol>
228 * <li><strong>Data transfer</strong>: Copy genotype data to GPU memory</li>
229 * <li><strong>Kernel setup</strong>: Configure kernel arguments and work group parameters</li>
230 * <li><strong>Kernel execution</strong>: Launch OpenCL kernels for fitness computation</li>
231 * <li><strong>Result retrieval</strong>: Read fitness values from GPU memory</li>
232 * <li><strong>Data conversion</strong>: Convert GPU results to appropriate fitness type</li>
233 * </ol>
234 *
235 * @param openCLExecutionContext the OpenCL execution context providing device access
236 * @param executorService the executor service for asynchronous operations
237 * @param generation the current generation number for context
238 * @param genotypes the genotypes to evaluate on this device
239 * @return a CompletableFuture that will complete with fitness values for each genotype
240 * @throws RuntimeException if GPU evaluation fails or setup errors occur
241 */
242 public abstract CompletableFuture<List<T>> compute(final OpenCLExecutionContext openCLExecutionContext,
243 final ExecutorService executorService, final long generation, final List<Genotype> genotypes);
244
245 /**
246 * Per-device cleanup hook called after each device partition evaluation.
247 *
248 * <p>This method is called for each device after its partition evaluation completes, providing an opportunity for
249 * device-specific cleanup and resource management.
250 *
251 * <p>Typical use cases:
252 * <ul>
253 * <li>Clean up temporary GPU memory allocations</li>
254 * <li>Log device-specific performance metrics</li>
255 * <li>Update device-specific statistics or state</li>
256 * <li>Perform device-specific validation or debugging</li>
257 * </ul>
258 *
259 * @param openCLExecutionContext the OpenCL execution context for this device
260 * @param executorService the executor service for asynchronous operations
261 * @param generation the current generation number (0-based)
262 * @param genotypes the partition of genotypes that were evaluated on this device
263 * @see #beforeEvaluation(OpenCLExecutionContext, ExecutorService, long, List)
264 */
265 public void afterEvaluation(final OpenCLExecutionContext openCLExecutionContext,
266 final ExecutorService executorService, final long generation, final List<Genotype> genotypes) {
267 }
268
269 /**
270 * Global cleanup hook called after each generation evaluation.
271 *
272 * <p>This method is called after fitness evaluation of each generation completes across all devices, providing an
273 * opportunity for global cleanup and statistics collection that applies to the entire population.
274 *
275 * <p>Typical use cases:
276 * <ul>
277 * <li>Log generation completion and performance metrics</li>
278 * <li>Update global statistics or progress tracking</li>
279 * <li>Perform global validation or debugging</li>
280 * <li>Clean up generation-specific global resources</li>
281 * </ul>
282 *
283 * @param generation the current generation number (0-based)
284 * @param genotypes the complete population that was evaluated
285 * @see #beforeEvaluation(long, List)
286 */
287 public void afterEvaluation(final long generation, final List<Genotype> genotypes) {
288 }
289
290 /**
291 * Per-device cleanup hook called for each OpenCL execution context at the end.
292 *
293 * <p>This method is called once for each OpenCL device when fitness evaluation is complete, providing an opportunity
294 * to clean up device-specific resources that were allocated in
295 * {@link #beforeAllEvaluations(OpenCLExecutionContext, ExecutorService)}.
296 *
297 * <p>Typical use cases:
298 * <ul>
299 * <li>Release GPU memory buffers and resources</li>
300 * <li>Clean up device-specific data structures</li>
301 * <li>Log device-specific performance summaries</li>
302 * <li>Ensure no GPU memory leaks occur</li>
303 * </ul>
304 *
305 * <p>This method should ensure proper cleanup even if exceptions occurred during evaluation, as it may be the only
306 * opportunity to prevent resource leaks.
307 *
308 * @param openCLExecutionContext the OpenCL execution context for this device
309 * @param executorService the executor service for asynchronous operations
310 * @see #beforeAllEvaluations(OpenCLExecutionContext, ExecutorService)
311 */
312 public void afterAllEvaluations(final OpenCLExecutionContext openCLExecutionContext,
313 final ExecutorService executorService) {
314 }
315
316 /**
317 * Global cleanup hook called once after all fitness evaluations complete.
318 *
319 * <p>This method is called once at the end of the evolutionary algorithm execution, after all OpenCL contexts have
320 * been cleaned up and all evaluations are complete. Use this method for final global cleanup and resource
321 * deallocation.
322 *
323 * <p>Typical use cases:
324 * <ul>
325 * <li>Clean up global resources and data structures</li>
326 * <li>Log final performance summaries and statistics</li>
327 * <li>Save results or generate reports</li>
328 * <li>Perform final validation or cleanup</li>
329 * </ul>
330 *
331 * <p>This method is called on the main thread after all concurrent operations complete.
332 *
333 * @see #beforeAllEvaluations()
334 */
335 public void afterAllEvaluations() {
336 }
337 }