View Javadoc
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   * 	&#64;code
46   * 	public class MyGPUFitness extends OpenCLFitness<Double> {
47   * 
48   * 		private CLData inputBuffer;
49   * 		private CLData outputBuffer;
50   * 
51   * 		&#64;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   * 		&#64;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 }