kizzasi_logic/
gpu_acceleration.rs1use crate::constraint::ViolationComputable;
21use crate::error::LogicResult;
22use scirs2_core::ndarray::{Array1, Array2};
23
24pub struct GPUConstraintChecker<C> {
28 constraints: Vec<C>,
30 gpu_available: bool,
32 gpu_threshold: usize,
34}
35
36fn check_gpu_availability() -> bool {
38 #[cfg(feature = "gpu")]
41 {
42 true
43 }
44 #[cfg(not(feature = "gpu"))]
45 {
46 false
47 }
48}
49
50impl<C: ViolationComputable + Clone> GPUConstraintChecker<C> {
51 pub fn new(constraints: Vec<C>) -> Self {
53 Self {
54 constraints,
55 gpu_available: check_gpu_availability(),
56 gpu_threshold: 1000, }
58 }
59
60 pub fn with_gpu_threshold(mut self, threshold: usize) -> Self {
62 self.gpu_threshold = threshold;
63 self
64 }
65
66 pub fn check_batch(&self, points: &Array2<f32>) -> Vec<bool> {
70 let (n_points, _) = points.dim();
71
72 if self.gpu_available && n_points >= self.gpu_threshold {
73 self.check_batch_gpu(points)
74 } else {
75 self.check_batch_cpu(points)
76 }
77 }
78
79 fn check_batch_cpu(&self, points: &Array2<f32>) -> Vec<bool> {
81 let (n_points, _) = points.dim();
82 let mut results = Vec::with_capacity(n_points);
83
84 for i in 0..n_points {
85 let point = points.row(i);
86 let point_slice: Vec<f32> = point.iter().copied().collect();
87 let satisfied = self.constraints.iter().all(|c| c.check(&point_slice));
88 results.push(satisfied);
89 }
90
91 results
92 }
93
94 fn check_batch_gpu(&self, points: &Array2<f32>) -> Vec<bool> {
98 #[cfg(feature = "gpu")]
99 {
100 self.check_batch_gpu_impl(points)
101 }
102 #[cfg(not(feature = "gpu"))]
103 {
104 self.check_batch_cpu(points)
106 }
107 }
108
109 #[cfg(feature = "gpu")]
110 fn check_batch_gpu_impl(&self, points: &Array2<f32>) -> Vec<bool> {
112 self.check_batch_cpu(points)
120 }
121
122 pub fn violation_batch(&self, points: &Array2<f32>) -> Vec<f32> {
124 let (n_points, _) = points.dim();
125
126 if self.gpu_available && n_points >= self.gpu_threshold {
127 self.violation_batch_gpu(points)
128 } else {
129 self.violation_batch_cpu(points)
130 }
131 }
132
133 fn violation_batch_cpu(&self, points: &Array2<f32>) -> Vec<f32> {
135 let (n_points, _) = points.dim();
136 let mut violations = Vec::with_capacity(n_points);
137
138 for i in 0..n_points {
139 let point = points.row(i);
140 let point_slice: Vec<f32> = point.iter().copied().collect();
141
142 let total_violation: f32 = self
143 .constraints
144 .iter()
145 .map(|c| c.violation(&point_slice))
146 .sum();
147
148 violations.push(total_violation);
149 }
150
151 violations
152 }
153
154 fn violation_batch_gpu(&self, points: &Array2<f32>) -> Vec<f32> {
156 #[cfg(feature = "gpu")]
157 {
158 self.violation_batch_gpu_impl(points)
159 }
160 #[cfg(not(feature = "gpu"))]
161 {
162 self.violation_batch_cpu(points)
163 }
164 }
165
166 #[cfg(feature = "gpu")]
167 fn violation_batch_gpu_impl(&self, points: &Array2<f32>) -> Vec<f32> {
169 self.violation_batch_cpu(points)
171 }
172
173 pub fn is_gpu_available(&self) -> bool {
175 self.gpu_available
176 }
177
178 pub fn num_constraints(&self) -> usize {
180 self.constraints.len()
181 }
182}
183
184pub struct GPUProjector {
186 max_iterations: usize,
188 tolerance: f32,
190 gpu_available: bool,
192}
193
194impl GPUProjector {
195 pub fn new() -> Self {
197 Self {
198 max_iterations: 100,
199 tolerance: 1e-6,
200 gpu_available: check_gpu_availability(),
201 }
202 }
203
204 pub fn with_max_iterations(mut self, max_iter: usize) -> Self {
206 self.max_iterations = max_iter;
207 self
208 }
209
210 pub fn with_tolerance(mut self, tol: f32) -> Self {
212 self.tolerance = tol;
213 self
214 }
215
216 pub fn project_batch<C: ViolationComputable + Clone>(
220 &self,
221 points: &Array2<f32>,
222 constraints: &[C],
223 ) -> LogicResult<Array2<f32>> {
224 let (n_points, n_dims) = points.dim();
225
226 if n_points == 0 {
227 return Ok(points.clone());
228 }
229
230 let mut projected = points.clone();
233
234 for i in 0..n_points {
235 let point = projected.row(i).to_owned();
236 let projected_point = self.project_point(&point, constraints)?;
237
238 for j in 0..n_dims {
239 projected[[i, j]] = projected_point[j];
240 }
241 }
242
243 Ok(projected)
244 }
245
246 fn project_point<C: ViolationComputable>(
248 &self,
249 point: &Array1<f32>,
250 constraints: &[C],
251 ) -> LogicResult<Array1<f32>> {
252 let mut x = point.clone();
253 let step_size = 0.01;
254
255 for _iter in 0..self.max_iterations {
256 let mut gradient = Array1::<f32>::zeros(x.len());
257 let mut total_violation = 0.0;
258
259 for constraint in constraints {
261 let x_slice: Vec<f32> = x.iter().copied().collect();
262 let violation = constraint.violation(&x_slice);
263 total_violation += violation;
264
265 if violation > 0.0 {
266 let eps = 1e-5;
268 for i in 0..x.len() {
269 let mut x_plus = x_slice.clone();
270 x_plus[i] += eps;
271 let violation_plus = constraint.violation(&x_plus);
272
273 gradient[i] += (violation_plus - violation) / eps;
274 }
275 }
276 }
277
278 if total_violation < self.tolerance {
279 break;
280 }
281
282 x = &x - &(&gradient * step_size);
284 }
285
286 Ok(x)
287 }
288
289 pub fn is_gpu_available(&self) -> bool {
291 self.gpu_available
292 }
293}
294
295impl Default for GPUProjector {
296 fn default() -> Self {
297 Self::new()
298 }
299}
300
301pub struct GPUGradientComputer {
303 epsilon: f32,
305 gpu_available: bool,
307}
308
309impl GPUGradientComputer {
310 pub fn new() -> Self {
312 Self {
313 epsilon: 1e-5,
314 gpu_available: check_gpu_availability(),
315 }
316 }
317
318 pub fn with_epsilon(mut self, eps: f32) -> Self {
320 self.epsilon = eps;
321 self
322 }
323
324 pub fn compute_batch_gradients<C: ViolationComputable + Clone>(
328 &self,
329 points: &Array2<f32>,
330 constraint: &C,
331 ) -> LogicResult<Array2<f32>> {
332 let (n_points, n_dims) = points.dim();
333 let mut gradients = Array2::zeros((n_points, n_dims));
334
335 for i in 0..n_points {
336 let point = points.row(i);
337 let point_slice: Vec<f32> = point.iter().copied().collect();
338 let base_violation = constraint.violation(&point_slice);
339
340 for j in 0..n_dims {
341 let mut perturbed = point_slice.clone();
342 perturbed[j] += self.epsilon;
343 let perturbed_violation = constraint.violation(&perturbed);
344
345 gradients[[i, j]] = (perturbed_violation - base_violation) / self.epsilon;
346 }
347 }
348
349 Ok(gradients)
350 }
351
352 pub fn compute_hessian<C: ViolationComputable>(
356 &self,
357 point: &[f32],
358 constraint: &C,
359 ) -> LogicResult<Array2<f32>> {
360 let n_dims = point.len();
361 let mut hessian = Array2::zeros((n_dims, n_dims));
362
363 for i in 0..n_dims {
365 for j in 0..n_dims {
366 let mut x_ij = point.to_vec();
367 let mut x_i = point.to_vec();
368 let mut x_j = point.to_vec();
369
370 x_ij[i] += self.epsilon;
371 x_ij[j] += self.epsilon;
372 x_i[i] += self.epsilon;
373 x_j[j] += self.epsilon;
374
375 let f_ij = constraint.violation(&x_ij);
376 let f_i = constraint.violation(&x_i);
377 let f_j = constraint.violation(&x_j);
378 let f_0 = constraint.violation(point);
379
380 hessian[[i, j]] = (f_ij - f_i - f_j + f_0) / (self.epsilon * self.epsilon);
381 }
382 }
383
384 Ok(hessian)
385 }
386
387 pub fn is_gpu_available(&self) -> bool {
389 self.gpu_available
390 }
391}
392
393impl Default for GPUGradientComputer {
394 fn default() -> Self {
395 Self::new()
396 }
397}
398
399#[cfg(test)]
400mod tests {
401 use super::*;
402 use crate::constraint::ConstraintBuilder;
403
404 #[test]
405 fn test_gpu_constraint_checker() {
406 let constraint1 = ConstraintBuilder::new()
407 .name("c1")
408 .less_than(10.0)
409 .build()
410 .unwrap();
411
412 let constraint2 = ConstraintBuilder::new()
413 .name("c2")
414 .greater_than(0.0)
415 .build()
416 .unwrap();
417
418 let checker = GPUConstraintChecker::new(vec![constraint1, constraint2]);
419
420 assert_eq!(checker.num_constraints(), 2);
421
422 let points = Array2::from_shape_vec((3, 1), vec![5.0, 15.0, -1.0]).unwrap();
424 let results = checker.check_batch(&points);
425
426 assert_eq!(results.len(), 3);
427 assert!(results[0]); assert!(!results[1]); assert!(!results[2]); }
431
432 #[test]
433 fn test_gpu_violation_batch() {
434 let constraint = ConstraintBuilder::new()
435 .name("c1")
436 .less_than(5.0)
437 .build()
438 .unwrap();
439
440 let checker = GPUConstraintChecker::new(vec![constraint]);
441
442 let points = Array2::from_shape_vec((3, 1), vec![3.0, 5.0, 7.0]).unwrap();
443 let violations = checker.violation_batch(&points);
444
445 assert_eq!(violations.len(), 3);
446 assert!(violations[0] < 1e-5); assert!(violations[1] < 1e-5); assert!(violations[2] > 0.0); }
450
451 #[test]
452 fn test_gpu_projector() {
453 let projector = GPUProjector::new().with_max_iterations(1000);
455
456 let constraint = ConstraintBuilder::new()
457 .name("c1")
458 .less_than(5.0)
459 .build()
460 .unwrap();
461
462 let point = Array1::from_vec(vec![7.0]);
464 let projected = projector.project_point(&point, &[constraint]).unwrap();
465
466 println!("Projected value: {}", projected[0]);
467 assert!(
468 projected[0] <= 5.1,
469 "Projected value {} is greater than 5.1",
470 projected[0]
471 );
472 }
473
474 #[test]
475 fn test_gpu_gradient_computer() {
476 let grad_computer = GPUGradientComputer::new();
477
478 let constraint = ConstraintBuilder::new()
479 .name("c1")
480 .less_than(5.0)
481 .build()
482 .unwrap();
483
484 let points = Array2::from_shape_vec((2, 1), vec![3.0, 7.0]).unwrap();
485 let gradients = grad_computer
486 .compute_batch_gradients(&points, &constraint)
487 .unwrap();
488
489 assert_eq!(gradients.dim(), (2, 1));
490 assert!(gradients[[0, 0]].abs() < 0.1);
492 assert!(gradients[[1, 0]] > 0.0);
493 }
494
495 #[test]
496 fn test_gpu_hessian() {
497 let grad_computer = GPUGradientComputer::new();
498
499 let constraint = ConstraintBuilder::new()
500 .name("c1")
501 .less_than(5.0)
502 .build()
503 .unwrap();
504
505 let point = vec![3.0];
506 let hessian = grad_computer.compute_hessian(&point, &constraint).unwrap();
507
508 assert_eq!(hessian.dim(), (1, 1));
509 assert!(hessian[[0, 0]].abs() < 0.1);
511 }
512
513 #[test]
514 fn test_gpu_threshold() {
515 let constraint = ConstraintBuilder::new()
516 .name("c1")
517 .less_than(10.0)
518 .build()
519 .unwrap();
520
521 let checker = GPUConstraintChecker::new(vec![constraint]).with_gpu_threshold(500);
522
523 let small_batch = Array2::from_shape_vec((10, 1), vec![1.0; 10]).unwrap();
525 let _results = checker.check_batch(&small_batch);
526
527 let large_batch = Array2::from_shape_vec((1000, 1), vec![1.0; 1000]).unwrap();
529 let _results = checker.check_batch(&large_batch);
530 }
531}