1#![allow(dead_code)]
29
30use oxicuda_blas::GpuFloat;
31
32use crate::error::{SparseError, SparseResult};
33use crate::format::CsrMatrix;
34use crate::handle::SparseHandle;
35
36fn to_f64<T: GpuFloat>(val: T) -> f64 {
41 if T::SIZE == 4 {
42 f32::from_bits(val.to_bits_u64() as u32) as f64
43 } else {
44 f64::from_bits(val.to_bits_u64())
45 }
46}
47
48fn from_f64<T: GpuFloat>(val: f64) -> T {
49 if T::SIZE == 4 {
50 T::from_bits_u64(u64::from((val as f32).to_bits()))
51 } else {
52 T::from_bits_u64(val.to_bits())
53 }
54}
55
56pub struct GraphColoring {
65 pub num_colors: usize,
67 pub colors: Vec<u32>,
69 pub color_offsets: Vec<usize>,
73 pub color_order: Vec<u32>,
75}
76
77impl GraphColoring {
78 pub fn from_csr<T: GpuFloat>(
92 _handle: &SparseHandle,
93 matrix: &CsrMatrix<T>,
94 ) -> SparseResult<Self> {
95 if matrix.rows() != matrix.cols() {
96 return Err(SparseError::DimensionMismatch(format!(
97 "GraphColoring requires square matrix, got {}x{}",
98 matrix.rows(),
99 matrix.cols()
100 )));
101 }
102
103 let n = matrix.rows() as usize;
104 if n == 0 {
105 return Ok(Self {
106 num_colors: 0,
107 colors: Vec::new(),
108 color_offsets: vec![0],
109 color_order: Vec::new(),
110 });
111 }
112
113 let (h_row_ptr, h_col_idx, _h_values) = matrix.to_host()?;
115
116 Self::from_csr_host(&h_row_ptr, &h_col_idx, n)
117 }
118
119 pub fn from_csr_host(row_ptr: &[i32], col_idx: &[i32], n: usize) -> SparseResult<Self> {
123 if n == 0 {
124 return Ok(Self {
125 num_colors: 0,
126 colors: Vec::new(),
127 color_offsets: vec![0],
128 color_order: Vec::new(),
129 });
130 }
131
132 let mut colors = vec![u32::MAX; n];
133 let mut max_color: u32 = 0;
134
135 let mut forbidden = Vec::new();
138
139 for i in 0..n {
140 let row_start = row_ptr[i] as usize;
141 let row_end = row_ptr[i + 1] as usize;
142
143 for f in forbidden.iter_mut() {
145 *f = false;
146 }
147
148 for nz in row_start..row_end {
150 let j = col_idx[nz] as usize;
151 if j == i || j >= n {
152 continue;
153 }
154 if colors[j] != u32::MAX {
156 let c = colors[j] as usize;
157 if c >= forbidden.len() {
158 forbidden.resize(c + 1, false);
159 }
160 forbidden[c] = true;
161 }
162
163 let j_start = row_ptr[j] as usize;
165 let j_end = row_ptr[j + 1] as usize;
166 for &col in &col_idx[j_start..j_end] {
167 let k = col as usize;
168 if k == i || k >= n {
169 continue;
170 }
171 if colors[k] != u32::MAX {
172 let c = colors[k] as usize;
173 if c >= forbidden.len() {
174 forbidden.resize(c + 1, false);
175 }
176 forbidden[c] = true;
177 }
178 }
179 }
180
181 let mut chosen = 0u32;
183 loop {
184 let c = chosen as usize;
185 if c >= forbidden.len() || !forbidden[c] {
186 break;
187 }
188 chosen += 1;
189 }
190
191 colors[i] = chosen;
192 if chosen > max_color {
193 max_color = chosen;
194 }
195 }
196
197 let num_colors = max_color as usize + 1;
198
199 let mut color_counts = vec![0usize; num_colors];
201 for &c in &colors {
202 color_counts[c as usize] += 1;
203 }
204
205 let mut color_offsets = vec![0usize; num_colors + 1];
206 for c in 0..num_colors {
207 color_offsets[c + 1] = color_offsets[c] + color_counts[c];
208 }
209
210 let mut color_order = vec![0u32; n];
211 let mut write_pos = color_offsets[..num_colors].to_vec();
212 for (i, &c) in colors.iter().enumerate() {
213 let c_usize = c as usize;
214 color_order[write_pos[c_usize]] = i as u32;
215 write_pos[c_usize] += 1;
216 }
217
218 Ok(Self {
219 num_colors,
220 colors,
221 color_offsets,
222 color_order,
223 })
224 }
225
226 pub fn rows_for_color(&self, color: usize) -> &[u32] {
232 if color >= self.num_colors {
233 return &[];
234 }
235 let start = self.color_offsets[color];
236 let end = self.color_offsets[color + 1];
237 &self.color_order[start..end]
238 }
239
240 pub fn parallel_ilu0<T: GpuFloat>(
257 &self,
258 _handle: &SparseHandle,
259 matrix: &CsrMatrix<T>,
260 ) -> SparseResult<(CsrMatrix<T>, CsrMatrix<T>)> {
261 let n = matrix.rows() as usize;
262 if n == 0 {
263 return Err(SparseError::InvalidArgument(
264 "cannot factor an empty matrix".to_string(),
265 ));
266 }
267
268 let (h_row_ptr, h_col_idx, h_values) = matrix.to_host()?;
269 let mut work = h_values;
270
271 for color in 0..self.num_colors {
273 let rows = self.rows_for_color(color);
274
275 for &row_u32 in rows {
277 let row = row_u32 as usize;
278 let row_start = h_row_ptr[row] as usize;
279 let row_end = h_row_ptr[row + 1] as usize;
280
281 for nz in row_start..row_end {
282 let k = h_col_idx[nz] as usize;
283 if k >= row {
284 break;
285 }
286
287 let k_start = h_row_ptr[k] as usize;
289 let k_end = h_row_ptr[k + 1] as usize;
290 let diag_pos = find_col_in_row(&h_col_idx[k_start..k_end], k as i32);
291 let diag_pos = match diag_pos {
292 Some(pos) => k_start + pos,
293 None => return Err(SparseError::SingularMatrix),
294 };
295
296 let a_kk = work[diag_pos];
297 if a_kk == T::gpu_zero() {
298 return Err(SparseError::SingularMatrix);
299 }
300
301 let ratio = div_gpu_float(work[nz], a_kk);
303 work[nz] = ratio;
304
305 for k_nz in (diag_pos + 1)..k_end {
307 let j = h_col_idx[k_nz];
308 if let Some(ij_off) = find_col_in_row(&h_col_idx[row_start..row_end], j) {
309 let ij_pos = row_start + ij_off;
310 let update = mul_gpu_float(ratio, work[k_nz]);
311 work[ij_pos] = sub_gpu_float(work[ij_pos], update);
312 }
313 }
314 }
315 }
316 }
317
318 split_lu(&h_row_ptr, &h_col_idx, &work, matrix.rows())
320 }
321
322 pub fn validate(&self, row_ptr: &[i32], col_idx: &[i32], n: usize) -> bool {
327 for i in 0..n {
328 let row_start = row_ptr[i] as usize;
329 let row_end = row_ptr[i + 1] as usize;
330 let ci = self.colors[i];
331
332 for nz in row_start..row_end {
334 let j = col_idx[nz] as usize;
335 if j == i || j >= n {
336 continue;
337 }
338 if self.colors[j] == ci {
339 return false;
340 }
341
342 let j_start = row_ptr[j] as usize;
344 let j_end = row_ptr[j + 1] as usize;
345 for &col in &col_idx[j_start..j_end] {
346 let k = col as usize;
347 if k == i || k >= n {
348 continue;
349 }
350 if self.colors[k] == ci {
351 return false;
352 }
353 }
354 }
355 }
356 true
357 }
358}
359
360fn find_col_in_row(col_slice: &[i32], target_col: i32) -> Option<usize> {
365 col_slice.iter().position(|&c| c == target_col)
366}
367
368fn div_gpu_float<T: GpuFloat>(a: T, b: T) -> T {
369 let fa = to_f64(a);
370 let fb = to_f64(b);
371 from_f64::<T>(fa / fb)
372}
373
374fn mul_gpu_float<T: GpuFloat>(a: T, b: T) -> T {
375 let fa = to_f64(a);
376 let fb = to_f64(b);
377 from_f64::<T>(fa * fb)
378}
379
380fn sub_gpu_float<T: GpuFloat>(a: T, b: T) -> T {
381 let fa = to_f64(a);
382 let fb = to_f64(b);
383 from_f64::<T>(fa - fb)
384}
385
386fn split_lu<T: GpuFloat>(
388 row_ptr: &[i32],
389 col_idx: &[i32],
390 values: &[T],
391 n: u32,
392) -> SparseResult<(CsrMatrix<T>, CsrMatrix<T>)> {
393 let n_usize = n as usize;
394
395 let mut l_nnz = 0usize;
396 let mut u_nnz = 0usize;
397 for i in 0..n_usize {
398 let start = row_ptr[i] as usize;
399 let end = row_ptr[i + 1] as usize;
400 for &cj in &col_idx[start..end] {
401 let j = cj as usize;
402 if j < i {
403 l_nnz += 1;
404 } else {
405 u_nnz += 1;
406 }
407 }
408 l_nnz += 1; }
410
411 let mut l_row_ptr = vec![0i32; n_usize + 1];
412 let mut l_col_idx = Vec::with_capacity(l_nnz);
413 let mut l_values = Vec::with_capacity(l_nnz);
414
415 let mut u_row_ptr = vec![0i32; n_usize + 1];
416 let mut u_col_idx = Vec::with_capacity(u_nnz);
417 let mut u_values = Vec::with_capacity(u_nnz);
418
419 for i in 0..n_usize {
420 let start = row_ptr[i] as usize;
421 let end = row_ptr[i + 1] as usize;
422
423 for idx in start..end {
424 let j = col_idx[idx] as usize;
425 if j < i {
426 l_col_idx.push(col_idx[idx]);
427 l_values.push(values[idx]);
428 }
429 }
430 l_col_idx.push(i as i32);
431 l_values.push(T::gpu_one());
432 l_row_ptr[i + 1] = l_col_idx.len() as i32;
433
434 for idx in start..end {
435 let j = col_idx[idx] as usize;
436 if j >= i {
437 u_col_idx.push(col_idx[idx]);
438 u_values.push(values[idx]);
439 }
440 }
441 u_row_ptr[i + 1] = u_col_idx.len() as i32;
442 }
443
444 let l_mat = CsrMatrix::from_host(n, n, &l_row_ptr, &l_col_idx, &l_values)?;
445 let u_mat = CsrMatrix::from_host(n, n, &u_row_ptr, &u_col_idx, &u_values)?;
446
447 Ok((l_mat, u_mat))
448}
449
450#[cfg(test)]
455mod tests {
456 use super::*;
457
458 #[test]
461 fn coloring_diagonal_matrix() {
462 let row_ptr = vec![0, 1, 2, 3];
464 let col_idx = vec![0, 1, 2];
465 let n = 3;
466
467 let coloring = GraphColoring::from_csr_host(&row_ptr, &col_idx, n);
468 assert!(coloring.is_ok());
469 let coloring = match coloring {
470 Ok(v) => v,
471 Err(e) => panic!("test: {e}"),
472 };
473 assert_eq!(coloring.num_colors, 1);
474 for &c in &coloring.colors {
475 assert_eq!(c, 0);
476 }
477 }
478
479 #[test]
480 fn coloring_tridiagonal() {
481 let row_ptr = vec![0, 2, 5, 8, 10];
487 let col_idx = vec![0, 1, 0, 1, 2, 1, 2, 3, 2, 3];
488 let n = 4;
489
490 let coloring = GraphColoring::from_csr_host(&row_ptr, &col_idx, n);
491 assert!(coloring.is_ok());
492 let coloring = match coloring {
493 Ok(v) => v,
494 Err(e) => panic!("test: {e}"),
495 };
496
497 assert!(coloring.num_colors >= 3, "need >= 3 colors for path graph");
499
500 assert!(coloring.validate(&row_ptr, &col_idx, n));
502 }
503
504 #[test]
505 fn coloring_dense_small() {
506 let row_ptr = vec![0, 3, 6, 9];
509 let col_idx = vec![0, 1, 2, 0, 1, 2, 0, 1, 2];
510 let n = 3;
511
512 let coloring = GraphColoring::from_csr_host(&row_ptr, &col_idx, n);
513 assert!(coloring.is_ok());
514 let coloring = match coloring {
515 Ok(v) => v,
516 Err(e) => panic!("test: {e}"),
517 };
518 assert_eq!(coloring.num_colors, 3);
519 assert!(coloring.validate(&row_ptr, &col_idx, n));
520 }
521
522 #[test]
523 fn coloring_rows_for_color() {
524 let row_ptr = vec![0, 1, 2, 3];
525 let col_idx = vec![0, 1, 2];
526 let n = 3;
527
528 let coloring = match GraphColoring::from_csr_host(&row_ptr, &col_idx, n) {
529 Ok(v) => v,
530 Err(e) => panic!("test: {e}"),
531 };
532
533 let rows = coloring.rows_for_color(0);
535 assert_eq!(rows.len(), 3);
536
537 let rows = coloring.rows_for_color(1);
539 assert_eq!(rows.len(), 0);
540 }
541
542 #[test]
543 fn coloring_single_node() {
544 let row_ptr = vec![0, 1];
546 let col_idx = vec![0];
547 let n = 1;
548
549 let coloring = GraphColoring::from_csr_host(&row_ptr, &col_idx, n);
550 assert!(coloring.is_ok());
551 let coloring = match coloring {
552 Ok(v) => v,
553 Err(e) => panic!("test: {e}"),
554 };
555 assert_eq!(coloring.num_colors, 1);
556 assert_eq!(coloring.colors[0], 0);
557 assert_eq!(coloring.color_order.len(), 1);
558 }
559
560 #[test]
561 fn coloring_color_offsets_consistency() {
562 let row_ptr = vec![0, 2, 5, 8, 11, 13];
564 let col_idx = vec![0, 1, 0, 1, 2, 1, 2, 3, 2, 3, 4, 3, 4];
565 let n = 5;
566
567 let coloring = match GraphColoring::from_csr_host(&row_ptr, &col_idx, n) {
568 Ok(v) => v,
569 Err(e) => panic!("test: {e}"),
570 };
571
572 let mut total = 0;
574 for c in 0..coloring.num_colors {
575 let count = coloring.rows_for_color(c).len();
576 total += count;
577 }
578 assert_eq!(total, n);
579
580 assert_eq!(
582 coloring.color_offsets[coloring.num_colors], n,
583 "last color offset should equal n"
584 );
585
586 assert!(coloring.validate(&row_ptr, &col_idx, n));
588 }
589
590 #[test]
591 fn coloring_validate_detects_bad_coloring() {
592 let row_ptr = vec![0, 2, 4, 6];
594 let col_idx = vec![0, 1, 1, 2, 0, 2]; let n = 3;
596
597 let bad_coloring = GraphColoring {
599 num_colors: 1,
600 colors: vec![0, 0, 0],
601 color_offsets: vec![0, 3],
602 color_order: vec![0, 1, 2],
603 };
604 assert!(!bad_coloring.validate(&row_ptr, &col_idx, n));
605 }
606
607 #[test]
610 fn host_float_arithmetic_f32() {
611 let a = 6.0_f32;
612 let b = 2.0_f32;
613 let result = div_gpu_float(a, b);
614 assert!((result - 3.0_f32).abs() < 1e-6);
615
616 let result = mul_gpu_float(a, b);
617 assert!((result - 12.0_f32).abs() < 1e-6);
618
619 let result = sub_gpu_float(a, b);
620 assert!((result - 4.0_f32).abs() < 1e-6);
621 }
622
623 #[test]
624 fn host_float_arithmetic_f64() {
625 let a = 6.0_f64;
626 let b = 2.0_f64;
627 assert!((div_gpu_float(a, b) - 3.0_f64).abs() < 1e-12);
628 assert!((mul_gpu_float(a, b) - 12.0_f64).abs() < 1e-12);
629 assert!((sub_gpu_float(a, b) - 4.0_f64).abs() < 1e-12);
630 }
631
632 #[test]
633 fn find_col_in_row_works() {
634 let cols = [0, 2, 5, 7];
635 assert_eq!(find_col_in_row(&cols, 2), Some(1));
636 assert_eq!(find_col_in_row(&cols, 3), None);
637 assert_eq!(find_col_in_row(&cols, 7), Some(3));
638 }
639}