1#![cfg_attr(not(feature = "cpu"), allow(dead_code))]
70
71use std::sync::Arc;
72
73use rlx_ir::{Graph, NodeId, register_op};
74
75#[cfg(feature = "cpu")]
76use rlx_cpu::op_registry::register_cpu_kernel;
77
78mod op_bicgstab;
82mod op_cg;
83mod op_cholesky;
84mod op_gmres;
85mod op_ilu_pcg;
86mod op_lsqr;
87mod op_lu;
88mod op_lu_general;
89mod op_mat_vec;
90mod op_pcg;
91mod op_transpose_values;
92mod op_values_grad;
93
94use op_bicgstab::*;
95use op_cg::*;
96use op_cholesky::*;
97use op_gmres::*;
98use op_ilu_pcg::*;
99use op_lsqr::*;
100use op_lu::*;
101use op_lu_general::*;
102use op_mat_vec::*;
103use op_pcg::*;
104use op_transpose_values::*;
105use op_values_grad::*;
106
107pub const SPARSE_LU_SOLVE: &str = "rlx_sparse.lu_solve";
108
109pub const SPARSE_MAT_VEC: &str = "rlx_sparse.mat_vec";
110
111pub const SPARSE_CG_SOLVE: &str = "rlx_sparse.cg_solve";
112
113pub const SPARSE_VALUES_GRAD: &str = "rlx_sparse.values_grad";
119
120pub const SPARSE_LU_SOLVE_GENERAL: &str = "rlx_sparse.lu_solve_general";
126
127pub const SPARSE_GMRES_SOLVE: &str = "rlx_sparse.gmres_solve";
132
133pub const SPARSE_TRANSPOSE_VALUES: &str = "rlx_sparse.transpose_values";
142
143pub const SPARSE_PCG_SOLVE: &str = "rlx_sparse.pcg_solve";
150
151pub const SPARSE_BICGSTAB_SOLVE: &str = "rlx_sparse.bicgstab_solve";
156
157pub const SPARSE_ILU_PCG_SOLVE: &str = "rlx_sparse.ilu_pcg_solve";
163
164pub const SPARSE_CHOLESKY_SOLVE: &str = "rlx_sparse.cholesky_solve";
169
170pub const SPARSE_LSQR_SOLVE: &str = "rlx_sparse.lsqr_solve";
176
177pub const SPARSE_SPGEMM: &str = "rlx_sparse.spgemm";
184
185#[cfg(feature = "cpu")]
194mod algos {
195 pub fn lu_solve(
196 values: &[f64],
197 col_idx: &[i32],
198 row_ptr: &[i32],
199 b: &[f64],
200 out: &mut [f64],
201 ) -> Result<(), String> {
202 let n = b.len();
203 if out.len() != n {
204 return Err(format!("sparse_lu: output len {} != b len {n}", out.len()));
205 }
206 if row_ptr.len() != n + 1 {
207 return Err(format!(
208 "sparse_lu: row_ptr len {} != n+1 ({})",
209 row_ptr.len(),
210 n + 1
211 ));
212 }
213 let mut a_dense = vec![0f64; n * n];
214 for r in 0..n {
215 for k in row_ptr[r] as usize..row_ptr[r + 1] as usize {
216 a_dense[r * n + col_idx[k] as usize] = values[k];
217 }
218 }
219 let mut b_copy = b.to_vec();
220 let info = rlx_cpu::blas::dgesv(&mut a_dense, &mut b_copy, n, 1);
221 if info != 0 {
222 return Err(format!(
223 "sparse_lu: dgesv returned info={info} (>0 → singular)"
224 ));
225 }
226 out.copy_from_slice(&b_copy);
227 Ok(())
228 }
229
230 pub fn mat_vec(
231 values: &[f64],
232 col_idx: &[i32],
233 row_ptr: &[i32],
234 x: &[f64],
235 out: &mut [f64],
236 ) -> Result<(), String> {
237 let n = x.len();
238 if out.len() != n {
239 return Err(format!("mat_vec: output len {} != x len {n}", out.len()));
240 }
241 if row_ptr.len() != n + 1 {
242 return Err(format!(
243 "mat_vec: row_ptr len {} != n+1 ({})",
244 row_ptr.len(),
245 n + 1
246 ));
247 }
248 for r in 0..n {
249 let mut acc = 0f64;
250 for k in row_ptr[r] as usize..row_ptr[r + 1] as usize {
251 acc += values[k] * x[col_idx[k] as usize];
252 }
253 out[r] = acc;
254 }
255 Ok(())
256 }
257
258 pub fn values_grad(
264 col_idx: &[i32],
265 row_ptr: &[i32],
266 u: &[f64],
267 v: &[f64],
268 out: &mut [f64],
269 ) -> Result<(), String> {
270 let n = u.len();
271 let nnz = col_idx.len();
272 if out.len() != nnz {
273 return Err(format!("values_grad: out len {} != nnz {nnz}", out.len()));
274 }
275 if row_ptr.len() != n + 1 {
276 return Err(format!(
277 "values_grad: row_ptr len {} != n+1 ({})",
278 row_ptr.len(),
279 n + 1
280 ));
281 }
282 let mut row_of_k = vec![0u32; nnz];
284 for r in 0..n {
285 let s = row_ptr[r] as usize;
286 let e = row_ptr[r + 1] as usize;
287 for k in s..e {
288 row_of_k[k] = r as u32;
289 }
290 }
291 for k in 0..nnz {
292 let r = row_of_k[k] as usize;
293 let c = col_idx[k] as usize;
294 if r >= n || c >= v.len() {
295 return Err(format!(
296 "values_grad: k={k} (row={r}, col={c}) out of bounds"
297 ));
298 }
299 out[k] = u[r] * v[c];
300 }
301 Ok(())
302 }
303
304 pub fn gmres_solve(
321 values: &[f64],
322 col_idx: &[i32],
323 row_ptr: &[i32],
324 b: &[f64],
325 out: &mut [f64],
326 max_iter: u32,
327 tol: f64,
328 ) -> Result<(), String> {
329 let n = b.len();
330 if out.len() != n {
331 return Err(format!("gmres_solve: out len {} != n {n}", out.len()));
332 }
333 if row_ptr.len() != n + 1 {
334 return Err(format!(
335 "gmres_solve: row_ptr len {} != n+1 ({})",
336 row_ptr.len(),
337 n + 1
338 ));
339 }
340 let m = max_iter.max(1) as usize;
341
342 let matvec = |x: &[f64], y: &mut [f64]| {
343 for r in 0..n {
344 let mut acc = 0f64;
345 for k in row_ptr[r] as usize..row_ptr[r + 1] as usize {
346 acc += values[k] * x[col_idx[k] as usize];
347 }
348 y[r] = acc;
349 }
350 };
351
352 let beta_init = b.iter().map(|v| v * v).sum::<f64>().sqrt();
354 if beta_init < tol {
355 for v in out.iter_mut() {
356 *v = 0.0;
357 }
358 return Ok(());
359 }
360
361 let mut v: Vec<Vec<f64>> = Vec::with_capacity(m + 1);
363 v.push(b.iter().map(|x| x / beta_init).collect());
364
365 let mut h: Vec<Vec<f64>> = Vec::with_capacity(m); let mut cs: Vec<f64> = Vec::with_capacity(m);
370 let mut sn: Vec<f64> = Vec::with_capacity(m);
371 let mut g: Vec<f64> = vec![0.0; m + 1];
372 g[0] = beta_init;
373
374 let mut converged_at: Option<usize> = None;
375 let mut w = vec![0f64; n];
376
377 for j in 0..m {
378 matvec(&v[j], &mut w);
379 let mut hcol = vec![0f64; j + 2];
381 for i in 0..=j {
382 hcol[i] = w.iter().zip(&v[i]).map(|(a, b)| a * b).sum();
383 for k in 0..n {
384 w[k] -= hcol[i] * v[i][k];
385 }
386 }
387 hcol[j + 1] = w.iter().map(|x| x * x).sum::<f64>().sqrt();
388 for i in 0..j {
390 let temp = cs[i] * hcol[i] + sn[i] * hcol[i + 1];
391 hcol[i + 1] = -sn[i] * hcol[i] + cs[i] * hcol[i + 1];
392 hcol[i] = temp;
393 }
394 let denom = (hcol[j] * hcol[j] + hcol[j + 1] * hcol[j + 1]).sqrt();
396 if denom == 0.0 {
397 return Err("gmres_solve: breakdown (denom = 0)".into());
398 }
399 let c = hcol[j] / denom;
400 let s = hcol[j + 1] / denom;
401 cs.push(c);
402 sn.push(s);
403 hcol[j] = c * hcol[j] + s * hcol[j + 1];
404 hcol[j + 1] = 0.0;
405 let g_temp = c * g[j] + s * g[j + 1];
407 g[j + 1] = -s * g[j] + c * g[j + 1];
408 g[j] = g_temp;
409 h.push(hcol);
410
411 if g[j + 1].abs() < tol {
413 converged_at = Some(j);
414 break;
415 }
416 if hcol_last_zero_check(&h[j]) {
417 converged_at = Some(j);
419 break;
420 }
421 if j + 1 < m {
422 let inv = 1.0 / hcol_subdiag(&h[j], j + 1).max(f64::MIN_POSITIVE);
423 let _ = inv;
424 let norm_w = w.iter().map(|x| x * x).sum::<f64>().sqrt();
428 if norm_w < f64::MIN_POSITIVE * 64.0 {
429 converged_at = Some(j);
430 break;
431 }
432 v.push(w.iter().map(|x| x / norm_w).collect());
433 }
434 }
435
436 let k = converged_at.map(|j| j + 1).unwrap_or(m);
438 let mut y = vec![0f64; k];
439 for i in (0..k).rev() {
440 let mut s = g[i];
441 for j in (i + 1)..k {
442 s -= h[j][i] * y[j];
443 }
444 y[i] = s / h[i][i];
445 }
446
447 for r in 0..n {
449 out[r] = 0.0;
450 }
451 for j in 0..k {
452 for r in 0..n {
453 out[r] += y[j] * v[j][r];
454 }
455 }
456 Ok(())
457 }
458
459 pub fn transpose_values(
464 values: &[f64],
465 col_idx: &[i32],
466 row_ptr: &[i32],
467 _col_idx_t: &[i32],
468 row_ptr_t: &[i32],
469 out: &mut [f64],
470 ) -> Result<(), String> {
471 let n = row_ptr.len().saturating_sub(1);
472 let nnz = values.len();
473 if out.len() != nnz {
474 return Err(format!(
475 "transpose_values: out len {} != nnz {nnz}",
476 out.len()
477 ));
478 }
479 let mut cursor: Vec<usize> = row_ptr_t.iter().map(|&x| x as usize).collect();
482 for r in 0..n {
483 let s = row_ptr[r] as usize;
484 let e = row_ptr[r + 1] as usize;
485 for k in s..e {
486 let c = col_idx[k] as usize;
487 let pos = cursor[c];
488 if pos >= nnz {
489 return Err(format!(
490 "transpose_values: cursor[{c}]={pos} ≥ nnz={nnz} \
491 (transposed pattern likely inconsistent with input)"
492 ));
493 }
494 out[pos] = values[k];
495 cursor[c] += 1;
496 }
497 }
498 Ok(())
499 }
500
501 pub fn pcg_solve(
508 values: &[f64],
509 col_idx: &[i32],
510 row_ptr: &[i32],
511 b: &[f64],
512 out: &mut [f64],
513 max_iter: u32,
514 tol: f64,
515 ) -> Result<(), String> {
516 let n = b.len();
517 if out.len() != n {
518 return Err(format!("pcg_solve: out len {} != n {n}", out.len()));
519 }
520 if row_ptr.len() != n + 1 {
521 return Err(format!(
522 "pcg_solve: row_ptr len {} != n+1 ({})",
523 row_ptr.len(),
524 n + 1
525 ));
526 }
527
528 let mut diag = vec![1.0f64; n];
534 for r in 0..n {
535 for k in row_ptr[r] as usize..row_ptr[r + 1] as usize {
536 if col_idx[k] as usize == r {
537 diag[r] = values[k].max(f64::MIN_POSITIVE);
538 break;
539 }
540 }
541 }
542 let inv_diag: Vec<f64> = diag.iter().map(|&d| 1.0 / d).collect();
543
544 let matvec = |x: &[f64], y: &mut [f64]| {
545 for r in 0..n {
546 let mut acc = 0f64;
547 for k in row_ptr[r] as usize..row_ptr[r + 1] as usize {
548 acc += values[k] * x[col_idx[k] as usize];
549 }
550 y[r] = acc;
551 }
552 };
553
554 let mut x = vec![0f64; n];
556 let mut r = b.to_vec();
557 let mut z: Vec<f64> = r.iter().zip(&inv_diag).map(|(rv, mi)| rv * mi).collect();
558 let mut p = z.clone();
559 let mut ap = vec![0f64; n];
560 let mut rho_old: f64 = r.iter().zip(&z).map(|(a, b)| a * b).sum();
561
562 for _ in 0..max_iter {
563 let r_norm: f64 = r.iter().map(|v| v * v).sum::<f64>().sqrt();
565 if r_norm < tol {
566 break;
567 }
568 matvec(&p, &mut ap);
569 let pap: f64 = p.iter().zip(&ap).map(|(a, b)| a * b).sum();
570 if pap == 0.0 {
571 return Err("pcg_solve: pᵀ·A·p = 0 (A is singular or not SPD)".into());
572 }
573 let alpha = rho_old / pap;
574 for i in 0..n {
575 x[i] += alpha * p[i];
576 }
577 for i in 0..n {
578 r[i] -= alpha * ap[i];
579 }
580 for i in 0..n {
581 z[i] = r[i] * inv_diag[i];
582 }
583 let rho_new: f64 = r.iter().zip(&z).map(|(a, b)| a * b).sum();
584 let beta = rho_new / rho_old;
585 for i in 0..n {
586 p[i] = z[i] + beta * p[i];
587 }
588 rho_old = rho_new;
589 }
590
591 out.copy_from_slice(&x);
592 Ok(())
593 }
594
595 pub fn cholesky_solve(
600 values: &[f64],
601 col_idx: &[i32],
602 row_ptr: &[i32],
603 b: &[f64],
604 out: &mut [f64],
605 ) -> Result<(), String> {
606 let n = b.len();
607 if out.len() != n {
608 return Err(format!("cholesky_solve: out len {} != n {n}", out.len()));
609 }
610 if row_ptr.len() != n + 1 {
611 return Err(format!(
612 "cholesky_solve: row_ptr len {} != n+1",
613 row_ptr.len()
614 ));
615 }
616 let mut a_dense = vec![0f64; n * n];
617 for r in 0..n {
618 for k in row_ptr[r] as usize..row_ptr[r + 1] as usize {
619 a_dense[r * n + col_idx[k] as usize] = values[k];
620 }
621 }
622 let info = rlx_cpu::blas::dpotrf(&mut a_dense, n, true);
624 if info != 0 {
625 return Err(format!("cholesky_solve: dpotrf info={info} (not SPD?)"));
626 }
627 let mut x = b.to_vec();
629 rlx_cpu::blas::dtrsm_lower_or_upper(
630 &a_dense, &mut x, n, 1, true, false,
631 );
632 rlx_cpu::blas::dtrsm_lower_or_upper(
634 &a_dense, &mut x, n, 1, true, true,
635 );
636 out.copy_from_slice(&x);
637 Ok(())
638 }
639
640 pub fn bicgstab(
644 values: &[f64],
645 col_idx: &[i32],
646 row_ptr: &[i32],
647 b: &[f64],
648 out: &mut [f64],
649 max_iter: u32,
650 tol: f64,
651 transpose_a: bool,
652 ) -> Result<(), String> {
653 let n = b.len();
654 if out.len() != n {
655 return Err(format!("bicgstab: out len {} != n {n}", out.len()));
656 }
657 if row_ptr.len() != n + 1 {
658 return Err(format!(
659 "bicgstab: row_ptr len {} != n+1 ({})",
660 row_ptr.len(),
661 n + 1
662 ));
663 }
664 let matvec = |x: &[f64], y: &mut [f64]| {
665 if !transpose_a {
666 for r in 0..n {
667 let mut acc = 0f64;
668 for k in row_ptr[r] as usize..row_ptr[r + 1] as usize {
669 acc += values[k] * x[col_idx[k] as usize];
670 }
671 y[r] = acc;
672 }
673 } else {
674 for v in y.iter_mut() {
675 *v = 0.0;
676 }
677 for r in 0..n {
678 for k in row_ptr[r] as usize..row_ptr[r + 1] as usize {
679 y[col_idx[k] as usize] += values[k] * x[r];
680 }
681 }
682 }
683 };
684 let mut x = vec![0f64; n];
685 let mut r = b.to_vec();
686 let r_hat = r.clone();
687 let mut p = r.clone();
688 let mut v = vec![0f64; n];
689 let mut s = vec![0f64; n];
690 let mut t = vec![0f64; n];
691 let mut rho_old: f64 = r_hat.iter().zip(&r).map(|(a, b)| a * b).sum();
692
693 for _ in 0..max_iter {
694 let r_norm: f64 = r.iter().map(|v| v * v).sum::<f64>().sqrt();
695 if r_norm < tol {
696 break;
697 }
698 matvec(&p, &mut v);
699 let rh_v: f64 = r_hat.iter().zip(&v).map(|(a, b)| a * b).sum();
700 if rh_v == 0.0 {
701 return Err("bicgstab: breakdown r̂·v = 0".into());
702 }
703 let alpha = rho_old / rh_v;
704 for i in 0..n {
705 s[i] = r[i] - alpha * v[i];
706 }
707 let s_norm: f64 = s.iter().map(|v| v * v).sum::<f64>().sqrt();
708 if s_norm < tol {
709 for i in 0..n {
710 x[i] += alpha * p[i];
711 }
712 r[..n].copy_from_slice(&s[..n]);
713 break;
714 }
715 matvec(&s, &mut t);
716 let tt: f64 = t.iter().map(|v| v * v).sum();
717 if tt == 0.0 {
718 return Err("bicgstab: breakdown t·t = 0".into());
719 }
720 let ts: f64 = t.iter().zip(&s).map(|(a, b)| a * b).sum();
721 let omega = ts / tt;
722 for i in 0..n {
723 x[i] += alpha * p[i] + omega * s[i];
724 r[i] = s[i] - omega * t[i];
725 }
726 if omega == 0.0 {
727 return Err("bicgstab: ω = 0 (stagnation)".into());
728 }
729 let rho_new: f64 = r_hat.iter().zip(&r).map(|(a, b)| a * b).sum();
730 if rho_old == 0.0 {
731 return Err("bicgstab: ρ_old = 0".into());
732 }
733 let beta = (rho_new / rho_old) * (alpha / omega);
734 for i in 0..n {
735 p[i] = r[i] + beta * (p[i] - omega * v[i]);
736 }
737 rho_old = rho_new;
738 }
739 out.copy_from_slice(&x);
740 Ok(())
741 }
742
743 pub fn lsqr_solve(
753 values: &[f64],
754 col_idx: &[i32],
755 row_ptr: &[i32],
756 b: &[f64],
757 out: &mut [f64],
758 max_iter: u32,
759 tol: f64,
760 n_cols: usize,
761 ) -> Result<(), String> {
762 let m = b.len();
763 let n = n_cols;
764 if out.len() != n {
765 return Err(format!("lsqr: out len {} != n {n}", out.len()));
766 }
767 if row_ptr.len() != m + 1 {
768 return Err(format!(
769 "lsqr: row_ptr len {} != m+1 ({})",
770 row_ptr.len(),
771 m + 1
772 ));
773 }
774
775 let av = |x: &[f64], y: &mut [f64]| {
777 for r in 0..m {
778 let mut acc = 0f64;
779 for k in row_ptr[r] as usize..row_ptr[r + 1] as usize {
780 acc += values[k] * x[col_idx[k] as usize];
781 }
782 y[r] = acc;
783 }
784 };
785 let atv = |u: &[f64], y: &mut [f64]| {
787 for v in y.iter_mut() {
788 *v = 0.0;
789 }
790 for r in 0..m {
791 for k in row_ptr[r] as usize..row_ptr[r + 1] as usize {
792 y[col_idx[k] as usize] += values[k] * u[r];
793 }
794 }
795 };
796
797 let mut u = b.to_vec();
798 let mut beta: f64 = u.iter().map(|v| v * v).sum::<f64>().sqrt();
799 if beta == 0.0 {
800 for v in out.iter_mut() {
801 *v = 0.0;
802 }
803 return Ok(());
804 }
805 for v in u.iter_mut() {
806 *v /= beta;
807 }
808
809 let mut v = vec![0f64; n];
810 atv(&u, &mut v);
811 let mut alpha: f64 = v.iter().map(|x| x * x).sum::<f64>().sqrt();
812 if alpha == 0.0 {
813 for v in out.iter_mut() {
814 *v = 0.0;
815 }
816 return Ok(());
817 }
818 for x in v.iter_mut() {
819 *x /= alpha;
820 }
821
822 let mut x = vec![0f64; n];
823 let mut w = v.clone();
824 let mut phi_bar = beta;
825 let mut rho_bar = alpha;
826
827 let mut tmp_u = vec![0f64; m];
828 let mut tmp_v = vec![0f64; n];
829
830 for _ in 0..max_iter {
831 av(&v, &mut tmp_u);
834 for i in 0..m {
835 tmp_u[i] -= alpha * u[i];
836 }
837 beta = tmp_u.iter().map(|x| x * x).sum::<f64>().sqrt();
838 if beta != 0.0 {
839 for i in 0..m {
840 u[i] = tmp_u[i] / beta;
841 }
842 atv(&u, &mut tmp_v);
844 for i in 0..n {
845 tmp_v[i] -= beta * v[i];
846 }
847 alpha = tmp_v.iter().map(|x| x * x).sum::<f64>().sqrt();
848 if alpha != 0.0 {
849 for i in 0..n {
850 v[i] = tmp_v[i] / alpha;
851 }
852 }
853 }
854
855 let rho = (rho_bar * rho_bar + beta * beta).sqrt();
857 let c = rho_bar / rho;
858 let s = beta / rho;
859 let theta = s * alpha;
860 rho_bar = -c * alpha;
861 let phi = c * phi_bar;
862 phi_bar *= s;
863
864 let phi_over_rho = phi / rho;
866 let theta_over_rho = theta / rho;
867 for i in 0..n {
868 x[i] += phi_over_rho * w[i];
869 w[i] = v[i] - theta_over_rho * w[i];
870 }
871
872 if phi_bar.abs() < tol {
873 break;
874 }
875 if alpha == 0.0 || beta == 0.0 {
876 break;
877 }
878 }
879 out.copy_from_slice(&x);
880 Ok(())
881 }
882
883 pub fn ilu0_factor(
887 values: &[f64],
888 col_idx: &[i32],
889 row_ptr: &[i32],
890 n: usize,
891 out_fact: &mut [f64],
892 ) -> Result<(), String> {
893 if out_fact.len() != values.len() {
894 return Err(format!(
895 "ilu0: out len {} != values len {}",
896 out_fact.len(),
897 values.len()
898 ));
899 }
900 out_fact.copy_from_slice(values);
901 for i in 0..n {
902 let row_i_start = row_ptr[i] as usize;
903 let row_i_end = row_ptr[i + 1] as usize;
904 for k in row_i_start..row_i_end {
905 let j = col_idx[k] as usize;
906 if j >= i {
907 break;
908 }
909 let row_j_start = row_ptr[j] as usize;
911 let row_j_end = row_ptr[j + 1] as usize;
912 let mut a_jj = 0f64;
913 let mut found = false;
914 for kj in row_j_start..row_j_end {
915 if col_idx[kj] as usize == j {
916 a_jj = out_fact[kj];
917 found = true;
918 break;
919 }
920 }
921 if !found || a_jj == 0.0 {
922 return Err(format!("ilu0: zero/missing diag at row {j}"));
923 }
924 out_fact[k] /= a_jj;
925 let lij = out_fact[k];
926 for kk in (k + 1)..row_i_end {
927 let m = col_idx[kk] as usize;
928 for kj in row_j_start..row_j_end {
929 if col_idx[kj] as usize == m {
930 out_fact[kk] -= lij * out_fact[kj];
931 break;
932 }
933 }
934 }
935 }
936 }
937 Ok(())
938 }
939
940 pub fn ilu0_apply(
943 fact: &[f64],
944 col_idx: &[i32],
945 row_ptr: &[i32],
946 n: usize,
947 b: &[f64],
948 out: &mut [f64],
949 ) {
950 for i in 0..n {
952 let mut acc = b[i];
953 for k in row_ptr[i] as usize..row_ptr[i + 1] as usize {
954 let j = col_idx[k] as usize;
955 if j < i {
956 acc -= fact[k] * out[j];
957 } else {
958 break;
959 }
960 }
961 out[i] = acc;
962 }
963 for i in (0..n).rev() {
965 let mut acc = out[i];
966 let mut diag = 1f64;
967 for k in row_ptr[i] as usize..row_ptr[i + 1] as usize {
968 let j = col_idx[k] as usize;
969 if j > i {
970 acc -= fact[k] * out[j];
971 } else if j == i {
972 diag = fact[k];
973 }
974 }
975 out[i] = acc / diag;
976 }
977 }
978
979 pub fn ilu_pcg_solve(
982 values: &[f64],
983 col_idx: &[i32],
984 row_ptr: &[i32],
985 b: &[f64],
986 out: &mut [f64],
987 max_iter: u32,
988 tol: f64,
989 ) -> Result<(), String> {
990 let n = b.len();
991 if out.len() != n {
992 return Err(format!("ilu_pcg: out len {} != n {n}", out.len()));
993 }
994 let mut fact = vec![0f64; values.len()];
995 ilu0_factor(values, col_idx, row_ptr, n, &mut fact)?;
996 let matvec = |x: &[f64], y: &mut [f64]| {
997 for r in 0..n {
998 let mut acc = 0f64;
999 for k in row_ptr[r] as usize..row_ptr[r + 1] as usize {
1000 acc += values[k] * x[col_idx[k] as usize];
1001 }
1002 y[r] = acc;
1003 }
1004 };
1005 let mut x = vec![0f64; n];
1006 let mut r = b.to_vec();
1007 let mut z = vec![0f64; n];
1008 ilu0_apply(&fact, col_idx, row_ptr, n, &r, &mut z);
1009 let mut p = z.clone();
1010 let mut ap = vec![0f64; n];
1011 let mut rho_old: f64 = r.iter().zip(&z).map(|(a, b)| a * b).sum();
1012 for _ in 0..max_iter {
1013 let r_norm: f64 = r.iter().map(|v| v * v).sum::<f64>().sqrt();
1014 if r_norm < tol {
1015 break;
1016 }
1017 matvec(&p, &mut ap);
1018 let pap: f64 = p.iter().zip(&ap).map(|(a, b)| a * b).sum();
1019 if pap == 0.0 {
1020 return Err("ilu_pcg: pᵀ·A·p = 0".into());
1021 }
1022 let alpha = rho_old / pap;
1023 for i in 0..n {
1024 x[i] += alpha * p[i];
1025 }
1026 for i in 0..n {
1027 r[i] -= alpha * ap[i];
1028 }
1029 ilu0_apply(&fact, col_idx, row_ptr, n, &r, &mut z);
1030 let rho_new: f64 = r.iter().zip(&z).map(|(a, b)| a * b).sum();
1031 let beta = rho_new / rho_old;
1032 for i in 0..n {
1033 p[i] = z[i] + beta * p[i];
1034 }
1035 rho_old = rho_new;
1036 }
1037 out.copy_from_slice(&x);
1038 Ok(())
1039 }
1040
1041 pub fn spgemm_csr(
1046 a_values: &[f64],
1047 a_col_idx: &[i32],
1048 a_row_ptr: &[i32],
1049 b_values: &[f64],
1050 b_col_idx: &[i32],
1051 b_row_ptr: &[i32],
1052 m: usize,
1053 k: usize,
1054 n: usize,
1055 ) -> Result<(Vec<f64>, Vec<i32>, Vec<i32>), String> {
1056 if a_row_ptr.len() != m + 1 {
1057 return Err(format!("spgemm: a_row_ptr len {} != m+1", a_row_ptr.len()));
1058 }
1059 if b_row_ptr.len() != k + 1 {
1060 return Err(format!("spgemm: b_row_ptr len {} != k+1", b_row_ptr.len()));
1061 }
1062 let mut c_row_ptr = vec![0i32; m + 1];
1064 let mut c_col_idx: Vec<i32> = Vec::new();
1065 let mut c_values: Vec<f64> = Vec::new();
1066
1067 let mut marker = vec![-1i32; n];
1069 let mut spa_vals = vec![0f64; n];
1070 let mut spa_cols: Vec<usize> = Vec::with_capacity(n);
1071
1072 for i in 0..m {
1073 spa_cols.clear();
1074 for ka in a_row_ptr[i] as usize..a_row_ptr[i + 1] as usize {
1075 let j = a_col_idx[ka] as usize;
1076 let aij = a_values[ka];
1077 for kb in b_row_ptr[j] as usize..b_row_ptr[j + 1] as usize {
1078 let l = b_col_idx[kb] as usize;
1079 let bjl = b_values[kb];
1080 if marker[l] != i as i32 {
1081 marker[l] = i as i32;
1082 spa_vals[l] = aij * bjl;
1083 spa_cols.push(l);
1084 } else {
1085 spa_vals[l] += aij * bjl;
1086 }
1087 }
1088 }
1089 spa_cols.sort_unstable();
1091 for &l in &spa_cols {
1092 c_col_idx.push(l as i32);
1093 c_values.push(spa_vals[l]);
1094 }
1095 c_row_ptr[i + 1] = c_col_idx.len() as i32;
1096 }
1097 Ok((c_values, c_col_idx, c_row_ptr))
1098 }
1099
1100 fn hcol_last_zero_check(hcol: &[f64]) -> bool {
1101 hcol.iter().all(|v| v.abs() < f64::MIN_POSITIVE * 64.0)
1107 }
1108 fn hcol_subdiag(hcol: &[f64], i: usize) -> f64 {
1109 hcol.get(i).copied().unwrap_or(0.0)
1110 }
1111
1112 pub fn cg_solve(
1113 values: &[f64],
1114 col_idx: &[i32],
1115 row_ptr: &[i32],
1116 b: &[f64],
1117 out: &mut [f64],
1118 max_iter: u32,
1119 tol: f64,
1120 ) -> Result<(), String> {
1121 let n = b.len();
1122 if out.len() != n {
1123 return Err(format!("cg_solve: output len {} != b len {n}", out.len()));
1124 }
1125 if row_ptr.len() != n + 1 {
1126 return Err(format!(
1127 "cg_solve: row_ptr len {} != n+1 ({})",
1128 row_ptr.len(),
1129 n + 1
1130 ));
1131 }
1132 let matvec = |x: &[f64], y: &mut [f64]| {
1133 for r in 0..n {
1134 let mut acc = 0f64;
1135 for k in row_ptr[r] as usize..row_ptr[r + 1] as usize {
1136 acc += values[k] * x[col_idx[k] as usize];
1137 }
1138 y[r] = acc;
1139 }
1140 };
1141 let mut x = vec![0f64; n];
1142 let mut r = b.to_vec();
1143 let mut p = r.clone();
1144 let mut ap = vec![0f64; n];
1145 let mut rs_old: f64 = r.iter().map(|v| v * v).sum();
1146 for _ in 0..max_iter {
1147 if rs_old.sqrt() < tol {
1148 break;
1149 }
1150 matvec(&p, &mut ap);
1151 let pap: f64 = p.iter().zip(&ap).map(|(a, b)| a * b).sum();
1152 if pap == 0.0 {
1153 return Err("cg_solve: pᵀ·A·p = 0 (A is singular or not SPD)".into());
1154 }
1155 let alpha = rs_old / pap;
1156 for i in 0..n {
1157 x[i] += alpha * p[i];
1158 }
1159 for i in 0..n {
1160 r[i] -= alpha * ap[i];
1161 }
1162 let rs_new: f64 = r.iter().map(|v| v * v).sum();
1163 let beta = rs_new / rs_old;
1164 for i in 0..n {
1165 p[i] = r[i] + beta * p[i];
1166 }
1167 rs_old = rs_new;
1168 }
1169 out.copy_from_slice(&x);
1170 Ok(())
1171 }
1172}
1173
1174pub fn encode_cg_attrs(max_iter: u32, tol: f64) -> Vec<u8> {
1179 let mut out = Vec::with_capacity(12);
1180 out.extend_from_slice(&max_iter.to_le_bytes());
1181 out.extend_from_slice(&tol.to_le_bytes());
1182 out
1183}
1184
1185#[cfg(feature = "cpu")]
1192pub fn spgemm_csr(
1193 a_values: &[f64],
1194 a_col_idx: &[i32],
1195 a_row_ptr: &[i32],
1196 b_values: &[f64],
1197 b_col_idx: &[i32],
1198 b_row_ptr: &[i32],
1199 m: usize,
1200 k: usize,
1201 n: usize,
1202) -> Result<(Vec<f64>, Vec<i32>, Vec<i32>), String> {
1203 algos::spgemm_csr(
1204 a_values, a_col_idx, a_row_ptr, b_values, b_col_idx, b_row_ptr, m, k, n,
1205 )
1206}
1207
1208pub fn csr_transpose_pattern(
1216 col_idx: &[i32],
1217 row_ptr: &[i32],
1218 n_rows: usize,
1219 n_cols: usize,
1220) -> (Vec<i32>, Vec<i32>) {
1221 let nnz = col_idx.len();
1222 let mut t_count = vec![0i32; n_cols];
1224 for &c in col_idx {
1225 t_count[c as usize] += 1;
1226 }
1227 let mut t_row_ptr = vec![0i32; n_cols + 1];
1228 for r in 0..n_cols {
1229 t_row_ptr[r + 1] = t_row_ptr[r] + t_count[r];
1230 }
1231 let mut t_col_idx = vec![0i32; nnz];
1232 let mut cursor = t_row_ptr.clone();
1233 for r in 0..n_rows {
1234 for k in row_ptr[r] as usize..row_ptr[r + 1] as usize {
1235 let c = col_idx[k] as usize;
1236 let pos = cursor[c] as usize;
1237 t_col_idx[pos] = r as i32;
1238 cursor[c] += 1;
1239 }
1240 }
1241 (t_col_idx, t_row_ptr)
1242}
1243
1244#[derive(Clone, Copy, Debug)]
1253pub struct SparseTensor {
1254 pub values: NodeId,
1256 pub col_idx: NodeId,
1258 pub row_ptr: NodeId,
1260 pub n_rows: usize,
1262 pub n_cols: usize,
1264}
1265
1266impl SparseTensor {
1267 pub fn from_csr(
1271 values: NodeId,
1272 col_idx: NodeId,
1273 row_ptr: NodeId,
1274 n_rows: usize,
1275 n_cols: usize,
1276 ) -> Self {
1277 Self {
1278 values,
1279 col_idx,
1280 row_ptr,
1281 n_rows,
1282 n_cols,
1283 }
1284 }
1285
1286 pub fn mat_vec(&self, g: &mut Graph, x: NodeId) -> NodeId {
1288 g.custom_op(
1289 SPARSE_MAT_VEC,
1290 Vec::new(),
1291 vec![self.values, self.col_idx, self.row_ptr, x],
1292 )
1293 }
1294
1295 pub fn solve(&self, g: &mut Graph, b: NodeId) -> NodeId {
1297 assert_eq!(
1298 self.n_rows, self.n_cols,
1299 "SparseTensor::solve requires a square matrix"
1300 );
1301 g.custom_op(
1302 SPARSE_LU_SOLVE,
1303 Vec::new(),
1304 vec![self.values, self.col_idx, self.row_ptr, b],
1305 )
1306 }
1307
1308 pub fn cg_solve(&self, g: &mut Graph, b: NodeId, max_iter: u32, tol: f64) -> NodeId {
1311 assert_eq!(
1312 self.n_rows, self.n_cols,
1313 "SparseTensor::cg_solve requires a square matrix"
1314 );
1315 g.custom_op(
1316 SPARSE_CG_SOLVE,
1317 encode_cg_attrs(max_iter, tol),
1318 vec![self.values, self.col_idx, self.row_ptr, b],
1319 )
1320 }
1321
1322 pub fn solve_general(&self, g: &mut Graph, b: NodeId, adjoint: &SparseTensor) -> NodeId {
1330 assert_eq!(
1331 self.n_rows, self.n_cols,
1332 "SparseTensor::solve_general requires a square matrix"
1333 );
1334 assert_eq!(
1335 adjoint.n_rows, self.n_cols,
1336 "adjoint shape mismatch: A is {}×{}, Aᵀ should be {}×{}",
1337 self.n_rows, self.n_cols, self.n_cols, self.n_rows
1338 );
1339 g.custom_op(
1340 SPARSE_LU_SOLVE_GENERAL,
1341 Vec::new(),
1342 vec![
1343 self.values,
1344 self.col_idx,
1345 self.row_ptr,
1346 b,
1347 adjoint.values,
1348 adjoint.col_idx,
1349 adjoint.row_ptr,
1350 ],
1351 )
1352 }
1353
1354 pub fn pcg_solve(&self, g: &mut Graph, b: NodeId, max_iter: u32, tol: f64) -> NodeId {
1361 assert_eq!(
1362 self.n_rows, self.n_cols,
1363 "SparseTensor::pcg_solve requires a square matrix"
1364 );
1365 g.custom_op(
1366 SPARSE_PCG_SOLVE,
1367 encode_cg_attrs(max_iter, tol),
1368 vec![self.values, self.col_idx, self.row_ptr, b],
1369 )
1370 }
1371
1372 pub fn transpose_values(&self, g: &mut Graph, col_idx_t: NodeId, row_ptr_t: NodeId) -> NodeId {
1378 g.custom_op(
1379 SPARSE_TRANSPOSE_VALUES,
1380 Vec::new(),
1381 vec![
1382 self.values,
1383 self.col_idx,
1384 self.row_ptr,
1385 col_idx_t,
1386 row_ptr_t,
1387 ],
1388 )
1389 }
1390
1391 pub fn cholesky_solve(&self, g: &mut Graph, b: NodeId) -> NodeId {
1396 assert_eq!(
1397 self.n_rows, self.n_cols,
1398 "SparseTensor::cholesky_solve requires a square matrix"
1399 );
1400 g.custom_op(
1401 SPARSE_CHOLESKY_SOLVE,
1402 Vec::new(),
1403 vec![self.values, self.col_idx, self.row_ptr, b],
1404 )
1405 }
1406
1407 pub fn lsqr_solve(&self, g: &mut Graph, b: NodeId, max_iter: u32, tol: f64) -> NodeId {
1412 let mut attrs = Vec::with_capacity(16);
1413 attrs.extend_from_slice(&max_iter.to_le_bytes());
1414 attrs.extend_from_slice(&tol.to_le_bytes());
1415 attrs.extend_from_slice(&(self.n_cols as u32).to_le_bytes());
1416 g.custom_op(
1417 SPARSE_LSQR_SOLVE,
1418 attrs,
1419 vec![self.values, self.col_idx, self.row_ptr, b],
1420 )
1421 }
1422
1423 pub fn bicgstab_solve(&self, g: &mut Graph, b: NodeId, max_iter: u32, tol: f64) -> NodeId {
1427 assert_eq!(
1428 self.n_rows, self.n_cols,
1429 "SparseTensor::bicgstab_solve requires a square matrix"
1430 );
1431 let mut attrs = Vec::with_capacity(13);
1432 attrs.extend_from_slice(&max_iter.to_le_bytes());
1433 attrs.extend_from_slice(&tol.to_le_bytes());
1434 attrs.push(0); g.custom_op(
1436 SPARSE_BICGSTAB_SOLVE,
1437 attrs,
1438 vec![self.values, self.col_idx, self.row_ptr, b],
1439 )
1440 }
1441
1442 pub fn ilu_pcg_solve(&self, g: &mut Graph, b: NodeId, max_iter: u32, tol: f64) -> NodeId {
1447 assert_eq!(
1448 self.n_rows, self.n_cols,
1449 "SparseTensor::ilu_pcg_solve requires a square matrix"
1450 );
1451 g.custom_op(
1452 SPARSE_ILU_PCG_SOLVE,
1453 encode_cg_attrs(max_iter, tol),
1454 vec![self.values, self.col_idx, self.row_ptr, b],
1455 )
1456 }
1457
1458 pub fn gmres_solve(
1462 &self,
1463 g: &mut Graph,
1464 b: NodeId,
1465 max_iter: u32,
1466 tol: f64,
1467 adjoint: &SparseTensor,
1468 ) -> NodeId {
1469 assert_eq!(
1470 self.n_rows, self.n_cols,
1471 "SparseTensor::gmres_solve requires a square matrix"
1472 );
1473 assert_eq!(adjoint.n_rows, self.n_cols, "adjoint shape mismatch");
1474 g.custom_op(
1475 SPARSE_GMRES_SOLVE,
1476 encode_cg_attrs(max_iter, tol),
1477 vec![
1478 self.values,
1479 self.col_idx,
1480 self.row_ptr,
1481 b,
1482 adjoint.values,
1483 adjoint.col_idx,
1484 adjoint.row_ptr,
1485 ],
1486 )
1487 }
1488}
1489
1490#[cfg(all(feature = "metal", target_vendor = "apple", not(target_os = "watchos")))]
1502mod metal_kernels {
1503 use super::*;
1504 use rlx_ir::DType;
1505 use rlx_metal::op_registry::MetalKernel;
1506
1507 unsafe fn typed<'a, T: Copy>(
1512 bytes: &'a [u8],
1513 shape: &rlx_ir::Shape,
1514 want: DType,
1515 role: &str,
1516 ) -> Result<&'a [T], String> {
1517 if shape.dtype() != want {
1518 return Err(format!(
1519 "{role}: expected {want:?}, got {:?}",
1520 shape.dtype()
1521 ));
1522 }
1523 let n = shape
1524 .num_elements()
1525 .ok_or_else(|| format!("{role}: dynamic shape not supported"))?;
1526 let need = n * std::mem::size_of::<T>();
1527 if bytes.len() < need {
1528 return Err(format!("{role}: bytes {} < need {need}", bytes.len()));
1529 }
1530 Ok(unsafe { std::slice::from_raw_parts(bytes.as_ptr() as *const T, n) })
1531 }
1532
1533 unsafe fn typed_mut<'a, T: Copy>(
1534 bytes: &'a mut [u8],
1535 shape: &rlx_ir::Shape,
1536 want: DType,
1537 role: &str,
1538 ) -> Result<&'a mut [T], String> {
1539 if shape.dtype() != want {
1540 return Err(format!(
1541 "{role}: expected {want:?}, got {:?}",
1542 shape.dtype()
1543 ));
1544 }
1545 let n = shape
1546 .num_elements()
1547 .ok_or_else(|| format!("{role}: dynamic shape not supported"))?;
1548 let need = n * std::mem::size_of::<T>();
1549 if bytes.len() < need {
1550 return Err(format!("{role}: bytes {} < need {need}", bytes.len()));
1551 }
1552 Ok(unsafe { std::slice::from_raw_parts_mut(bytes.as_mut_ptr() as *mut T, n) })
1553 }
1554
1555 #[derive(Debug)]
1556 pub(super) struct SparseLuMetal;
1557 impl MetalKernel for SparseLuMetal {
1558 fn name(&self) -> &str {
1559 SPARSE_LU_SOLVE
1560 }
1561 fn execute(
1562 &self,
1563 inputs: &[(&[u8], &rlx_ir::Shape)],
1564 output: (&mut [u8], &rlx_ir::Shape),
1565 _attrs: &[u8],
1566 ) -> Result<(), String> {
1567 unsafe {
1568 let values = typed::<f64>(inputs[0].0, inputs[0].1, DType::F64, "values")?;
1569 let col_idx = typed::<i32>(inputs[1].0, inputs[1].1, DType::I32, "col_idx")?;
1570 let row_ptr = typed::<i32>(inputs[2].0, inputs[2].1, DType::I32, "row_ptr")?;
1571 let b = typed::<f64>(inputs[3].0, inputs[3].1, DType::F64, "b")?;
1572 let out = typed_mut::<f64>(output.0, output.1, DType::F64, "out")?;
1573 algos::lu_solve(values, col_idx, row_ptr, b, out)
1574 }
1575 }
1576 }
1577
1578 #[derive(Debug)]
1579 pub(super) struct SparseMatVecMetal;
1580 impl MetalKernel for SparseMatVecMetal {
1581 fn name(&self) -> &str {
1582 SPARSE_MAT_VEC
1583 }
1584 fn execute(
1585 &self,
1586 inputs: &[(&[u8], &rlx_ir::Shape)],
1587 output: (&mut [u8], &rlx_ir::Shape),
1588 _attrs: &[u8],
1589 ) -> Result<(), String> {
1590 unsafe {
1591 let values = typed::<f64>(inputs[0].0, inputs[0].1, DType::F64, "values")?;
1592 let col_idx = typed::<i32>(inputs[1].0, inputs[1].1, DType::I32, "col_idx")?;
1593 let row_ptr = typed::<i32>(inputs[2].0, inputs[2].1, DType::I32, "row_ptr")?;
1594 let x = typed::<f64>(inputs[3].0, inputs[3].1, DType::F64, "x")?;
1595 let out = typed_mut::<f64>(output.0, output.1, DType::F64, "out")?;
1596 algos::mat_vec(values, col_idx, row_ptr, x, out)
1597 }
1598 }
1599 }
1600
1601 #[derive(Debug)]
1602 pub(super) struct SparseCgMetal;
1603 impl MetalKernel for SparseCgMetal {
1604 fn name(&self) -> &str {
1605 SPARSE_CG_SOLVE
1606 }
1607 fn execute(
1608 &self,
1609 inputs: &[(&[u8], &rlx_ir::Shape)],
1610 output: (&mut [u8], &rlx_ir::Shape),
1611 attrs: &[u8],
1612 ) -> Result<(), String> {
1613 let (max_iter, tol) = decode_cg_attrs(attrs)?;
1614 unsafe {
1615 let values = typed::<f64>(inputs[0].0, inputs[0].1, DType::F64, "values")?;
1616 let col_idx = typed::<i32>(inputs[1].0, inputs[1].1, DType::I32, "col_idx")?;
1617 let row_ptr = typed::<i32>(inputs[2].0, inputs[2].1, DType::I32, "row_ptr")?;
1618 let b = typed::<f64>(inputs[3].0, inputs[3].1, DType::F64, "b")?;
1619 let out = typed_mut::<f64>(output.0, output.1, DType::F64, "out")?;
1620 algos::cg_solve(values, col_idx, row_ptr, b, out, max_iter, tol)
1621 }
1622 }
1623 }
1624
1625 #[derive(Debug)]
1626 pub(super) struct SparseValuesGradMetal;
1627 impl MetalKernel for SparseValuesGradMetal {
1628 fn name(&self) -> &str {
1629 SPARSE_VALUES_GRAD
1630 }
1631 fn execute(
1632 &self,
1633 inputs: &[(&[u8], &rlx_ir::Shape)],
1634 output: (&mut [u8], &rlx_ir::Shape),
1635 _attrs: &[u8],
1636 ) -> Result<(), String> {
1637 unsafe {
1638 let col_idx = typed::<i32>(inputs[0].0, inputs[0].1, DType::I32, "col_idx")?;
1639 let row_ptr = typed::<i32>(inputs[1].0, inputs[1].1, DType::I32, "row_ptr")?;
1640 let u = typed::<f64>(inputs[2].0, inputs[2].1, DType::F64, "u")?;
1641 let v = typed::<f64>(inputs[3].0, inputs[3].1, DType::F64, "v")?;
1642 let out = typed_mut::<f64>(output.0, output.1, DType::F64, "out")?;
1643 algos::values_grad(col_idx, row_ptr, u, v, out)
1644 }
1645 }
1646 }
1647
1648 #[derive(Debug)]
1649 pub(super) struct SparseLuGeneralMetal;
1650 impl MetalKernel for SparseLuGeneralMetal {
1651 fn name(&self) -> &str {
1652 SPARSE_LU_SOLVE_GENERAL
1653 }
1654 fn execute(
1655 &self,
1656 inputs: &[(&[u8], &rlx_ir::Shape)],
1657 output: (&mut [u8], &rlx_ir::Shape),
1658 _attrs: &[u8],
1659 ) -> Result<(), String> {
1660 unsafe {
1663 let values = typed::<f64>(inputs[0].0, inputs[0].1, DType::F64, "values")?;
1664 let col_idx = typed::<i32>(inputs[1].0, inputs[1].1, DType::I32, "col_idx")?;
1665 let row_ptr = typed::<i32>(inputs[2].0, inputs[2].1, DType::I32, "row_ptr")?;
1666 let b = typed::<f64>(inputs[3].0, inputs[3].1, DType::F64, "b")?;
1667 let out = typed_mut::<f64>(output.0, output.1, DType::F64, "out")?;
1668 algos::lu_solve(values, col_idx, row_ptr, b, out)
1669 }
1670 }
1671 }
1672
1673 #[derive(Debug)]
1674 pub(super) struct SparseGmresMetal;
1675 impl MetalKernel for SparseGmresMetal {
1676 fn name(&self) -> &str {
1677 SPARSE_GMRES_SOLVE
1678 }
1679 fn execute(
1680 &self,
1681 inputs: &[(&[u8], &rlx_ir::Shape)],
1682 output: (&mut [u8], &rlx_ir::Shape),
1683 attrs: &[u8],
1684 ) -> Result<(), String> {
1685 let (max_iter, tol) = decode_cg_attrs(attrs)?;
1686 unsafe {
1687 let values = typed::<f64>(inputs[0].0, inputs[0].1, DType::F64, "values")?;
1688 let col_idx = typed::<i32>(inputs[1].0, inputs[1].1, DType::I32, "col_idx")?;
1689 let row_ptr = typed::<i32>(inputs[2].0, inputs[2].1, DType::I32, "row_ptr")?;
1690 let b = typed::<f64>(inputs[3].0, inputs[3].1, DType::F64, "b")?;
1691 let out = typed_mut::<f64>(output.0, output.1, DType::F64, "out")?;
1692 algos::gmres_solve(values, col_idx, row_ptr, b, out, max_iter, tol)
1693 }
1694 }
1695 }
1696}
1697
1698#[cfg(all(feature = "mlx", target_os = "macos"))]
1715mod mlx_kernels {
1716 use super::*;
1717 use rlx_ir::DType;
1718 use rlx_mlx::array::{Array, MlxError};
1719 use rlx_mlx::op_registry::MlxKernel;
1720
1721 fn shape_dims_static(s: &rlx_ir::Shape) -> Result<Vec<usize>, MlxError> {
1722 s.dims()
1723 .iter()
1724 .map(|d| match d {
1725 rlx_ir::Dim::Static(n) => Ok(*n),
1726 _ => Err(MlxError(
1727 "rlx-sparse mlx kernel: dynamic shape not supported".into(),
1728 )),
1729 })
1730 .collect()
1731 }
1732
1733 fn bytes_to_f64(b: &[u8]) -> Vec<f64> {
1735 b.chunks_exact(8)
1736 .map(|c| f64::from_le_bytes(c.try_into().unwrap()))
1737 .collect()
1738 }
1739 fn bytes_to_i32(b: &[u8]) -> Vec<i32> {
1740 b.chunks_exact(4)
1741 .map(|c| i32::from_le_bytes(c.try_into().unwrap()))
1742 .collect()
1743 }
1744 fn f64_to_bytes(xs: &[f64]) -> Vec<u8> {
1745 let mut out = Vec::with_capacity(xs.len() * 8);
1746 for x in xs {
1747 out.extend_from_slice(&x.to_le_bytes());
1748 }
1749 out
1750 }
1751
1752 fn run_lu(inputs: &[&Array], output_shape: &rlx_ir::Shape) -> Result<Array, MlxError> {
1753 let values = bytes_to_f64(&inputs[0].to_bytes()?);
1754 let col_idx = bytes_to_i32(&inputs[1].to_bytes()?);
1755 let row_ptr = bytes_to_i32(&inputs[2].to_bytes()?);
1756 let b = bytes_to_f64(&inputs[3].to_bytes()?);
1757 let mut out = vec![0f64; b.len()];
1758 algos::lu_solve(&values, &col_idx, &row_ptr, &b, &mut out).map_err(MlxError)?;
1759 let dims = shape_dims_static(output_shape)?;
1760 Array::from_bytes(&f64_to_bytes(&out), &dims, DType::F64)
1761 }
1762
1763 fn run_mat_vec(inputs: &[&Array], output_shape: &rlx_ir::Shape) -> Result<Array, MlxError> {
1764 let values = bytes_to_f64(&inputs[0].to_bytes()?);
1765 let col_idx = bytes_to_i32(&inputs[1].to_bytes()?);
1766 let row_ptr = bytes_to_i32(&inputs[2].to_bytes()?);
1767 let x = bytes_to_f64(&inputs[3].to_bytes()?);
1768 let mut out = vec![0f64; x.len()];
1769 algos::mat_vec(&values, &col_idx, &row_ptr, &x, &mut out).map_err(MlxError)?;
1770 let dims = shape_dims_static(output_shape)?;
1771 Array::from_bytes(&f64_to_bytes(&out), &dims, DType::F64)
1772 }
1773
1774 fn run_cg(
1775 inputs: &[&Array],
1776 output_shape: &rlx_ir::Shape,
1777 attrs: &[u8],
1778 ) -> Result<Array, MlxError> {
1779 let (max_iter, tol) = decode_cg_attrs(attrs).map_err(MlxError)?;
1780 let values = bytes_to_f64(&inputs[0].to_bytes()?);
1781 let col_idx = bytes_to_i32(&inputs[1].to_bytes()?);
1782 let row_ptr = bytes_to_i32(&inputs[2].to_bytes()?);
1783 let b = bytes_to_f64(&inputs[3].to_bytes()?);
1784 let mut out = vec![0f64; b.len()];
1785 algos::cg_solve(&values, &col_idx, &row_ptr, &b, &mut out, max_iter, tol)
1786 .map_err(MlxError)?;
1787 let dims = shape_dims_static(output_shape)?;
1788 Array::from_bytes(&f64_to_bytes(&out), &dims, DType::F64)
1789 }
1790
1791 pub(super) struct SparseLuMlx;
1792 impl MlxKernel for SparseLuMlx {
1793 fn name(&self) -> &str {
1794 SPARSE_LU_SOLVE
1795 }
1796 fn execute(
1797 &self,
1798 inputs: &[&Array],
1799 out_shape: &rlx_ir::Shape,
1800 _attrs: &[u8],
1801 ) -> Result<Array, MlxError> {
1802 run_lu(inputs, out_shape)
1803 }
1804 }
1805 pub(super) struct SparseMatVecMlx;
1806 impl MlxKernel for SparseMatVecMlx {
1807 fn name(&self) -> &str {
1808 SPARSE_MAT_VEC
1809 }
1810 fn execute(
1811 &self,
1812 inputs: &[&Array],
1813 out_shape: &rlx_ir::Shape,
1814 _attrs: &[u8],
1815 ) -> Result<Array, MlxError> {
1816 run_mat_vec(inputs, out_shape)
1817 }
1818 }
1819 pub(super) struct SparseCgMlx;
1820 impl MlxKernel for SparseCgMlx {
1821 fn name(&self) -> &str {
1822 SPARSE_CG_SOLVE
1823 }
1824 fn execute(
1825 &self,
1826 inputs: &[&Array],
1827 out_shape: &rlx_ir::Shape,
1828 attrs: &[u8],
1829 ) -> Result<Array, MlxError> {
1830 run_cg(inputs, out_shape, attrs)
1831 }
1832 }
1833
1834 fn run_values_grad(inputs: &[&Array], output_shape: &rlx_ir::Shape) -> Result<Array, MlxError> {
1835 let col_idx = bytes_to_i32(&inputs[0].to_bytes()?);
1836 let row_ptr = bytes_to_i32(&inputs[1].to_bytes()?);
1837 let u = bytes_to_f64(&inputs[2].to_bytes()?);
1838 let v = bytes_to_f64(&inputs[3].to_bytes()?);
1839 let mut out = vec![0f64; col_idx.len()];
1840 algos::values_grad(&col_idx, &row_ptr, &u, &v, &mut out).map_err(MlxError)?;
1841 let dims = shape_dims_static(output_shape)?;
1842 Array::from_bytes(&f64_to_bytes(&out), &dims, DType::F64)
1843 }
1844
1845 fn run_lu_general(inputs: &[&Array], output_shape: &rlx_ir::Shape) -> Result<Array, MlxError> {
1846 let values = bytes_to_f64(&inputs[0].to_bytes()?);
1849 let col_idx = bytes_to_i32(&inputs[1].to_bytes()?);
1850 let row_ptr = bytes_to_i32(&inputs[2].to_bytes()?);
1851 let b = bytes_to_f64(&inputs[3].to_bytes()?);
1852 let mut out = vec![0f64; b.len()];
1853 algos::lu_solve(&values, &col_idx, &row_ptr, &b, &mut out).map_err(MlxError)?;
1854 let dims = shape_dims_static(output_shape)?;
1855 Array::from_bytes(&f64_to_bytes(&out), &dims, DType::F64)
1856 }
1857
1858 fn run_gmres(
1859 inputs: &[&Array],
1860 output_shape: &rlx_ir::Shape,
1861 attrs: &[u8],
1862 ) -> Result<Array, MlxError> {
1863 let (max_iter, tol) = decode_cg_attrs(attrs).map_err(MlxError)?;
1864 let values = bytes_to_f64(&inputs[0].to_bytes()?);
1865 let col_idx = bytes_to_i32(&inputs[1].to_bytes()?);
1866 let row_ptr = bytes_to_i32(&inputs[2].to_bytes()?);
1867 let b = bytes_to_f64(&inputs[3].to_bytes()?);
1868 let mut out = vec![0f64; b.len()];
1869 algos::gmres_solve(&values, &col_idx, &row_ptr, &b, &mut out, max_iter, tol)
1870 .map_err(MlxError)?;
1871 let dims = shape_dims_static(output_shape)?;
1872 Array::from_bytes(&f64_to_bytes(&out), &dims, DType::F64)
1873 }
1874
1875 pub(super) struct SparseValuesGradMlx;
1876 impl MlxKernel for SparseValuesGradMlx {
1877 fn name(&self) -> &str {
1878 SPARSE_VALUES_GRAD
1879 }
1880 fn execute(
1881 &self,
1882 inputs: &[&Array],
1883 out_shape: &rlx_ir::Shape,
1884 _attrs: &[u8],
1885 ) -> Result<Array, MlxError> {
1886 run_values_grad(inputs, out_shape)
1887 }
1888 }
1889 pub(super) struct SparseLuGeneralMlx;
1890 impl MlxKernel for SparseLuGeneralMlx {
1891 fn name(&self) -> &str {
1892 SPARSE_LU_SOLVE_GENERAL
1893 }
1894 fn execute(
1895 &self,
1896 inputs: &[&Array],
1897 out_shape: &rlx_ir::Shape,
1898 _attrs: &[u8],
1899 ) -> Result<Array, MlxError> {
1900 run_lu_general(inputs, out_shape)
1901 }
1902 }
1903 pub(super) struct SparseGmresMlx;
1904 impl MlxKernel for SparseGmresMlx {
1905 fn name(&self) -> &str {
1906 SPARSE_GMRES_SOLVE
1907 }
1908 fn execute(
1909 &self,
1910 inputs: &[&Array],
1911 out_shape: &rlx_ir::Shape,
1912 attrs: &[u8],
1913 ) -> Result<Array, MlxError> {
1914 run_gmres(inputs, out_shape, attrs)
1915 }
1916 }
1917}
1918
1919pub fn cg_solve(
1927 values: &[f64],
1928 col_idx: &[i32],
1929 row_ptr: &[i32],
1930 b: &[f64],
1931 out: &mut [f64],
1932 max_iter: u32,
1933 tol: f64,
1934) -> Result<(), String> {
1935 algos::cg_solve(values, col_idx, row_ptr, b, out, max_iter, tol)
1936}
1937
1938pub fn register() {
1939 register_op(Arc::new(SparseLuExt));
1940 register_op(Arc::new(SparseMatVecExt));
1941 register_op(Arc::new(SparseCgExt));
1942 register_op(Arc::new(SparseValuesGradExt));
1943 register_op(Arc::new(SparseLuGeneralExt));
1944 register_op(Arc::new(SparseGmresExt));
1945 register_op(Arc::new(SparseTransposeValuesExt));
1946 register_op(Arc::new(SparsePcgExt));
1947 register_op(Arc::new(SparseBicgstabExt));
1948 register_op(Arc::new(SparseIluPcgExt));
1949 register_op(Arc::new(SparseCholeskyExt));
1950 register_op(Arc::new(SparseLsqrExt));
1951
1952 #[cfg(feature = "cpu")]
1953 {
1954 register_cpu_kernel(Arc::new(SparseLuCpu));
1955 register_cpu_kernel(Arc::new(SparseMatVecCpu));
1956 register_cpu_kernel(Arc::new(SparseCgCpu));
1957 register_cpu_kernel(Arc::new(SparseValuesGradCpu));
1958 register_cpu_kernel(Arc::new(SparseLuGeneralCpu));
1959 register_cpu_kernel(Arc::new(SparseGmresCpu));
1960 register_cpu_kernel(Arc::new(SparseTransposeValuesCpu));
1961 register_cpu_kernel(Arc::new(SparsePcgCpu));
1962 register_cpu_kernel(Arc::new(SparseBicgstabCpu));
1963 register_cpu_kernel(Arc::new(SparseIluPcgCpu));
1964 register_cpu_kernel(Arc::new(SparseCholeskyCpu));
1965 register_cpu_kernel(Arc::new(SparseLsqrCpu));
1966 }
1967
1968 #[cfg(all(feature = "metal", target_vendor = "apple", not(target_os = "watchos")))]
1969 {
1970 use rlx_metal::op_registry::register_metal_kernel;
1971 register_metal_kernel(Arc::new(metal_kernels::SparseLuMetal));
1972 register_metal_kernel(Arc::new(metal_kernels::SparseMatVecMetal));
1973 register_metal_kernel(Arc::new(metal_kernels::SparseCgMetal));
1974 register_metal_kernel(Arc::new(metal_kernels::SparseValuesGradMetal));
1975 register_metal_kernel(Arc::new(metal_kernels::SparseLuGeneralMetal));
1976 register_metal_kernel(Arc::new(metal_kernels::SparseGmresMetal));
1977 }
1978
1979 #[cfg(all(feature = "mlx", target_os = "macos"))]
1980 {
1981 use rlx_mlx::op_registry::register_mlx_kernel;
1982 register_mlx_kernel(Arc::new(mlx_kernels::SparseLuMlx));
1983 register_mlx_kernel(Arc::new(mlx_kernels::SparseMatVecMlx));
1984 register_mlx_kernel(Arc::new(mlx_kernels::SparseCgMlx));
1985 register_mlx_kernel(Arc::new(mlx_kernels::SparseValuesGradMlx));
1986 register_mlx_kernel(Arc::new(mlx_kernels::SparseLuGeneralMlx));
1987 register_mlx_kernel(Arc::new(mlx_kernels::SparseGmresMlx));
1988 }
1989}