1#![allow(dead_code)]
27
28use std::collections::VecDeque;
29
30use oxicuda_blas::GpuFloat;
31
32use crate::error::{SparseError, SparseResult};
33use crate::format::CsrMatrix;
34
35pub fn rcm_ordering<T: GpuFloat>(matrix: &CsrMatrix<T>) -> SparseResult<Vec<usize>> {
60 if matrix.rows() != matrix.cols() {
61 return Err(SparseError::DimensionMismatch(format!(
62 "RCM requires square matrix, got {}x{}",
63 matrix.rows(),
64 matrix.cols()
65 )));
66 }
67
68 let n = matrix.rows() as usize;
69 if n == 0 {
70 return Ok(Vec::new());
71 }
72
73 let (h_row_ptr, h_col_idx, _) = matrix.to_host()?;
74 rcm_ordering_host(&h_row_ptr, &h_col_idx, n)
75}
76
77pub fn rcm_ordering_host(row_ptr: &[i32], col_idx: &[i32], n: usize) -> SparseResult<Vec<usize>> {
79 if n == 0 {
80 return Ok(Vec::new());
81 }
82
83 let degrees: Vec<usize> = (0..n)
85 .map(|i| {
86 let start = row_ptr[i] as usize;
87 let end = row_ptr[i + 1] as usize;
88 col_idx[start..end]
89 .iter()
90 .filter(|&&c| c as usize != i && (c as usize) < n)
91 .count()
92 })
93 .collect();
94
95 let start_node = find_pseudo_peripheral(row_ptr, col_idx, °rees, n);
97
98 let mut visited = vec![false; n];
100 let mut order = Vec::with_capacity(n);
101
102 let starts = [start_node];
104 let mut queue: VecDeque<usize> = VecDeque::new();
107 let mut component_start = 0;
108 while order.len() < n {
109 let root = if component_start < starts.len() {
110 starts[component_start]
111 } else {
112 match visited.iter().position(|&v| !v) {
114 Some(node) => node,
115 None => break,
116 }
117 };
118 component_start += 1;
119
120 if visited[root] {
121 continue;
122 }
123
124 visited[root] = true;
125 queue.push_back(root);
126
127 while let Some(node) = queue.pop_front() {
128 order.push(node);
129
130 let start = row_ptr[node] as usize;
132 let end = row_ptr[node + 1] as usize;
133 let mut neighbors: Vec<usize> = col_idx[start..end]
134 .iter()
135 .map(|&c| c as usize)
136 .filter(|&c| c < n && c != node && !visited[c])
137 .collect();
138
139 neighbors.sort_unstable();
141 neighbors.dedup();
142
143 neighbors.sort_by_key(|&nbr| degrees[nbr]);
145
146 for nbr in neighbors {
147 if !visited[nbr] {
148 visited[nbr] = true;
149 queue.push_back(nbr);
150 }
151 }
152 }
153 }
154
155 order.reverse();
157
158 Ok(order)
159}
160
161fn find_pseudo_peripheral(row_ptr: &[i32], col_idx: &[i32], degrees: &[usize], n: usize) -> usize {
166 let mut current = 0;
168 let mut min_deg = degrees[0];
169 for (i, &d) in degrees.iter().enumerate().skip(1) {
170 if d < min_deg {
171 min_deg = d;
172 current = i;
173 }
174 }
175
176 for _ in 0..5 {
178 let (last_level, _) = bfs_levels(row_ptr, col_idx, n, current);
179 if last_level.is_empty() {
180 break;
181 }
182 let mut best = last_level[0];
184 let mut best_deg = degrees[best];
185 for &node in &last_level[1..] {
186 if degrees[node] < best_deg {
187 best_deg = degrees[node];
188 best = node;
189 }
190 }
191 if best == current {
192 break;
193 }
194 current = best;
195 }
196
197 current
198}
199
200fn bfs_levels(row_ptr: &[i32], col_idx: &[i32], n: usize, root: usize) -> (Vec<usize>, usize) {
202 let mut visited = vec![false; n];
203 let mut current_level = Vec::new();
204 let mut next_level = Vec::new();
205
206 visited[root] = true;
207 current_level.push(root);
208 let mut num_levels = 1;
209
210 loop {
211 for &node in ¤t_level {
212 let start = row_ptr[node] as usize;
213 let end = row_ptr[node + 1] as usize;
214 for &c in &col_idx[start..end] {
215 let nbr = c as usize;
216 if nbr < n && !visited[nbr] {
217 visited[nbr] = true;
218 next_level.push(nbr);
219 }
220 }
221 }
222
223 if next_level.is_empty() {
224 break;
225 }
226
227 num_levels += 1;
228 current_level.clear();
229 std::mem::swap(&mut current_level, &mut next_level);
230 }
231
232 (current_level, num_levels)
233}
234
235pub fn amd_ordering<T: GpuFloat>(matrix: &CsrMatrix<T>) -> SparseResult<Vec<usize>> {
257 if matrix.rows() != matrix.cols() {
258 return Err(SparseError::DimensionMismatch(format!(
259 "AMD requires square matrix, got {}x{}",
260 matrix.rows(),
261 matrix.cols()
262 )));
263 }
264
265 let n = matrix.rows() as usize;
266 if n == 0 {
267 return Ok(Vec::new());
268 }
269
270 let (h_row_ptr, h_col_idx, _) = matrix.to_host()?;
271 amd_ordering_host(&h_row_ptr, &h_col_idx, n)
272}
273
274pub fn amd_ordering_host(row_ptr: &[i32], col_idx: &[i32], n: usize) -> SparseResult<Vec<usize>> {
276 if n == 0 {
277 return Ok(Vec::new());
278 }
279
280 let mut adj: Vec<Vec<usize>> = Vec::with_capacity(n);
282 for i in 0..n {
283 let start = row_ptr[i] as usize;
284 let end = row_ptr[i + 1] as usize;
285 let mut neighbors: Vec<usize> = col_idx[start..end]
286 .iter()
287 .map(|&c| c as usize)
288 .filter(|&c| c != i && c < n)
289 .collect();
290 neighbors.sort_unstable();
291 neighbors.dedup();
292 adj.push(neighbors);
293 }
294
295 let mut eliminated = vec![false; n];
296 let mut degree: Vec<usize> = adj.iter().map(|a| a.len()).collect();
297 let mut perm = Vec::with_capacity(n);
298
299 for _ in 0..n {
300 let mut min_node = None;
302 let mut min_deg = usize::MAX;
303 for (i, (&d, &elim)) in degree.iter().zip(eliminated.iter()).enumerate() {
304 if !elim && d < min_deg {
305 min_deg = d;
306 min_node = Some(i);
307 }
308 }
309
310 let node = match min_node {
311 Some(v) => v,
312 None => break,
313 };
314
315 perm.push(node);
316 eliminated[node] = true;
317
318 let neighbors: Vec<usize> = adj[node]
320 .iter()
321 .copied()
322 .filter(|&nbr| !eliminated[nbr])
323 .collect();
324
325 for &nbr in &neighbors {
328 adj[nbr].retain(|&x| x != node);
330
331 for &other in &neighbors {
333 if other != nbr && !adj[nbr].contains(&other) {
334 adj[nbr].push(other);
335 }
336 }
337
338 degree[nbr] = adj[nbr].iter().filter(|&&x| !eliminated[x]).count();
340 }
341
342 degree[node] = 0;
343 }
344
345 Ok(perm)
346}
347
348pub fn permute_csr<T: GpuFloat>(
367 matrix: &CsrMatrix<T>,
368 perm: &[usize],
369) -> SparseResult<CsrMatrix<T>> {
370 let n = matrix.rows() as usize;
371 if perm.len() != n {
372 return Err(SparseError::InvalidArgument(format!(
373 "permutation length ({}) must match matrix dimension ({})",
374 perm.len(),
375 n
376 )));
377 }
378
379 let inv_perm = inverse_permutation(perm);
381 if inv_perm.len() != n {
382 return Err(SparseError::InvalidArgument(
383 "invalid permutation: not a valid bijection".to_string(),
384 ));
385 }
386
387 let (h_row_ptr, h_col_idx, h_values) = matrix.to_host()?;
388
389 let mut new_row_ptr = vec![0i32; n + 1];
391 let mut new_entries: Vec<Vec<(i32, T)>> = Vec::with_capacity(n);
392
393 for new_row in 0..n {
394 let old_row = perm[new_row];
395 if old_row >= n {
396 return Err(SparseError::InvalidArgument(format!(
397 "permutation index {} out of bounds (n={})",
398 old_row, n
399 )));
400 }
401
402 let start = h_row_ptr[old_row] as usize;
403 let end = h_row_ptr[old_row + 1] as usize;
404
405 let mut entries: Vec<(i32, T)> = Vec::with_capacity(end - start);
406 for k in start..end {
407 let old_col = h_col_idx[k] as usize;
408 if old_col >= n {
409 return Err(SparseError::InvalidArgument(format!(
410 "column index {} out of bounds (n={})",
411 old_col, n
412 )));
413 }
414 let new_col = inv_perm[old_col];
415 entries.push((new_col as i32, h_values[k]));
416 }
417
418 entries.sort_by_key(|&(c, _)| c);
420
421 new_row_ptr[new_row + 1] = new_row_ptr[new_row] + entries.len() as i32;
422 new_entries.push(entries);
423 }
424
425 let nnz = new_row_ptr[n] as usize;
426 if nnz == 0 {
427 return Err(SparseError::ZeroNnz);
428 }
429
430 let mut new_col_idx = Vec::with_capacity(nnz);
431 let mut new_values = Vec::with_capacity(nnz);
432 for entries in &new_entries {
433 for &(c, v) in entries {
434 new_col_idx.push(c);
435 new_values.push(v);
436 }
437 }
438
439 CsrMatrix::from_host(
440 matrix.rows(),
441 matrix.cols(),
442 &new_row_ptr,
443 &new_col_idx,
444 &new_values,
445 )
446}
447
448pub fn inverse_permutation(perm: &[usize]) -> Vec<usize> {
452 let n = perm.len();
453 let mut inv = vec![0usize; n];
454 for (new_idx, &old_idx) in perm.iter().enumerate() {
455 if old_idx < n {
456 inv[old_idx] = new_idx;
457 }
458 }
459 inv
460}
461
462pub fn bandwidth(row_ptr: &[i32], col_idx: &[i32], n: usize) -> usize {
466 let mut bw = 0usize;
467 for i in 0..n {
468 let start = row_ptr[i] as usize;
469 let end = row_ptr[i + 1] as usize;
470 for &c in &col_idx[start..end] {
471 let j = c as usize;
472 let diff = i.abs_diff(j);
473 if diff > bw {
474 bw = diff;
475 }
476 }
477 }
478 bw
479}
480
481#[cfg(test)]
486mod tests {
487 use super::*;
488
489 #[test]
490 fn rcm_identity() {
491 let row_ptr = vec![0, 1, 2, 3];
493 let col_idx = vec![0, 1, 2];
494 let perm = rcm_ordering_host(&row_ptr, &col_idx, 3);
495 assert!(perm.is_ok());
496 let perm = perm.expect("test: should succeed");
497 assert_eq!(perm.len(), 3);
498 let mut sorted = perm.clone();
500 sorted.sort_unstable();
501 assert_eq!(sorted, vec![0, 1, 2]);
502 }
503
504 #[test]
505 fn rcm_tridiagonal() {
506 let row_ptr = vec![0, 2, 5, 8, 11, 13];
508 let col_idx = vec![0, 1, 0, 1, 2, 1, 2, 3, 2, 3, 4, 3, 4];
509 let perm = rcm_ordering_host(&row_ptr, &col_idx, 5);
510 assert!(perm.is_ok());
511 let perm = perm.expect("test: should succeed");
512 assert_eq!(perm.len(), 5);
513 let mut sorted = perm.clone();
515 sorted.sort_unstable();
516 assert_eq!(sorted, vec![0, 1, 2, 3, 4]);
517 }
518
519 #[test]
520 fn rcm_reduces_bandwidth() {
521 let row_ptr = vec![0, 5, 7, 9, 11, 13];
529 let col_idx = vec![0, 1, 2, 3, 4, 0, 1, 0, 2, 0, 3, 0, 4];
530 let n = 5;
531 let orig_bw = bandwidth(&row_ptr, &col_idx, n);
532 assert_eq!(orig_bw, 4);
533
534 let perm = rcm_ordering_host(&row_ptr, &col_idx, n);
535 assert!(perm.is_ok());
536 let perm = perm.expect("test: should succeed");
537
538 let inv = inverse_permutation(&perm);
540 let mut new_bw = 0;
541 for (i, &old_row) in perm.iter().enumerate().take(n) {
542 let start = row_ptr[old_row] as usize;
543 let end = row_ptr[old_row + 1] as usize;
544 for &c in &col_idx[start..end] {
545 let new_col = inv[c as usize];
546 let diff = i.abs_diff(new_col);
547 if diff > new_bw {
548 new_bw = diff;
549 }
550 }
551 }
552 assert!(new_bw <= orig_bw);
554 }
555
556 #[test]
557 fn amd_identity() {
558 let row_ptr = vec![0, 1, 2, 3];
559 let col_idx = vec![0, 1, 2];
560 let perm = amd_ordering_host(&row_ptr, &col_idx, 3);
561 assert!(perm.is_ok());
562 let perm = perm.expect("test: should succeed");
563 assert_eq!(perm.len(), 3);
564 let mut sorted = perm.clone();
565 sorted.sort_unstable();
566 assert_eq!(sorted, vec![0, 1, 2]);
567 }
568
569 #[test]
570 fn amd_tridiagonal() {
571 let row_ptr = vec![0, 2, 5, 8, 10];
572 let col_idx = vec![0, 1, 0, 1, 2, 1, 2, 3, 2, 3];
573 let perm = amd_ordering_host(&row_ptr, &col_idx, 4);
574 assert!(perm.is_ok());
575 let perm = perm.expect("test: should succeed");
576 assert_eq!(perm.len(), 4);
577 let mut sorted = perm.clone();
579 sorted.sort_unstable();
580 assert_eq!(sorted, vec![0, 1, 2, 3]);
581 }
582
583 #[test]
584 fn inverse_permutation_roundtrip() {
585 let perm = vec![3, 1, 0, 2];
586 let inv = inverse_permutation(&perm);
587 assert_eq!(inv, vec![2, 1, 3, 0]);
588
589 let inv_inv = inverse_permutation(&inv);
591 assert_eq!(inv_inv, perm);
592 }
593
594 #[test]
595 fn bandwidth_calculation() {
596 let row_ptr = vec![0, 2, 5, 7];
598 let col_idx = vec![0, 1, 0, 1, 2, 1, 2];
599 assert_eq!(bandwidth(&row_ptr, &col_idx, 3), 1);
600
601 let row_ptr = vec![0, 1, 2, 3];
603 let col_idx = vec![0, 1, 2];
604 assert_eq!(bandwidth(&row_ptr, &col_idx, 3), 0);
605 }
606
607 #[test]
608 fn rcm_empty() {
609 let perm = rcm_ordering_host(&[0], &[], 0);
610 assert!(perm.is_ok());
611 assert!(perm.expect("test: should succeed").is_empty());
612 }
613
614 #[test]
615 fn amd_empty() {
616 let perm = amd_ordering_host(&[0], &[], 0);
617 assert!(perm.is_ok());
618 assert!(perm.expect("test: should succeed").is_empty());
619 }
620}