#![cfg_attr(not(feature = "cpu"), allow(dead_code))]
use std::sync::Arc;
use rlx_ir::{Graph, NodeId, register_op};
#[cfg(feature = "cpu")]
use rlx_cpu::op_registry::register_cpu_kernel;
mod op_bicgstab;
mod op_cg;
mod op_cholesky;
mod op_gmres;
mod op_ilu_pcg;
mod op_lsqr;
mod op_lu;
mod op_lu_general;
mod op_mat_vec;
mod op_pcg;
mod op_transpose_values;
mod op_values_grad;
use op_bicgstab::*;
use op_cg::*;
use op_cholesky::*;
use op_gmres::*;
use op_ilu_pcg::*;
use op_lsqr::*;
use op_lu::*;
use op_lu_general::*;
use op_mat_vec::*;
use op_pcg::*;
use op_transpose_values::*;
use op_values_grad::*;
pub const SPARSE_LU_SOLVE: &str = "rlx_sparse.lu_solve";
pub const SPARSE_MAT_VEC: &str = "rlx_sparse.mat_vec";
pub const SPARSE_CG_SOLVE: &str = "rlx_sparse.cg_solve";
pub const SPARSE_VALUES_GRAD: &str = "rlx_sparse.values_grad";
pub const SPARSE_LU_SOLVE_GENERAL: &str = "rlx_sparse.lu_solve_general";
pub const SPARSE_GMRES_SOLVE: &str = "rlx_sparse.gmres_solve";
pub const SPARSE_TRANSPOSE_VALUES: &str = "rlx_sparse.transpose_values";
pub const SPARSE_PCG_SOLVE: &str = "rlx_sparse.pcg_solve";
pub const SPARSE_BICGSTAB_SOLVE: &str = "rlx_sparse.bicgstab_solve";
pub const SPARSE_ILU_PCG_SOLVE: &str = "rlx_sparse.ilu_pcg_solve";
pub const SPARSE_CHOLESKY_SOLVE: &str = "rlx_sparse.cholesky_solve";
pub const SPARSE_LSQR_SOLVE: &str = "rlx_sparse.lsqr_solve";
pub const SPARSE_SPGEMM: &str = "rlx_sparse.spgemm";
#[cfg(feature = "cpu")]
mod algos {
pub fn lu_solve(
values: &[f64],
col_idx: &[i32],
row_ptr: &[i32],
b: &[f64],
out: &mut [f64],
) -> Result<(), String> {
let n = b.len();
if out.len() != n {
return Err(format!("sparse_lu: output len {} != b len {n}", out.len()));
}
if row_ptr.len() != n + 1 {
return Err(format!(
"sparse_lu: row_ptr len {} != n+1 ({})",
row_ptr.len(),
n + 1
));
}
let mut a_dense = vec![0f64; n * n];
for r in 0..n {
for k in row_ptr[r] as usize..row_ptr[r + 1] as usize {
a_dense[r * n + col_idx[k] as usize] = values[k];
}
}
let mut b_copy = b.to_vec();
let info = rlx_cpu::blas::dgesv(&mut a_dense, &mut b_copy, n, 1);
if info != 0 {
return Err(format!(
"sparse_lu: dgesv returned info={info} (>0 → singular)"
));
}
out.copy_from_slice(&b_copy);
Ok(())
}
pub fn mat_vec(
values: &[f64],
col_idx: &[i32],
row_ptr: &[i32],
x: &[f64],
out: &mut [f64],
) -> Result<(), String> {
let n = x.len();
if out.len() != n {
return Err(format!("mat_vec: output len {} != x len {n}", out.len()));
}
if row_ptr.len() != n + 1 {
return Err(format!(
"mat_vec: row_ptr len {} != n+1 ({})",
row_ptr.len(),
n + 1
));
}
for r in 0..n {
let mut acc = 0f64;
for k in row_ptr[r] as usize..row_ptr[r + 1] as usize {
acc += values[k] * x[col_idx[k] as usize];
}
out[r] = acc;
}
Ok(())
}
pub fn values_grad(
col_idx: &[i32],
row_ptr: &[i32],
u: &[f64],
v: &[f64],
out: &mut [f64],
) -> Result<(), String> {
let n = u.len();
let nnz = col_idx.len();
if out.len() != nnz {
return Err(format!("values_grad: out len {} != nnz {nnz}", out.len()));
}
if row_ptr.len() != n + 1 {
return Err(format!(
"values_grad: row_ptr len {} != n+1 ({})",
row_ptr.len(),
n + 1
));
}
let mut row_of_k = vec![0u32; nnz];
for r in 0..n {
let s = row_ptr[r] as usize;
let e = row_ptr[r + 1] as usize;
for k in s..e {
row_of_k[k] = r as u32;
}
}
for k in 0..nnz {
let r = row_of_k[k] as usize;
let c = col_idx[k] as usize;
if r >= n || c >= v.len() {
return Err(format!(
"values_grad: k={k} (row={r}, col={c}) out of bounds"
));
}
out[k] = u[r] * v[c];
}
Ok(())
}
pub fn gmres_solve(
values: &[f64],
col_idx: &[i32],
row_ptr: &[i32],
b: &[f64],
out: &mut [f64],
max_iter: u32,
tol: f64,
) -> Result<(), String> {
let n = b.len();
if out.len() != n {
return Err(format!("gmres_solve: out len {} != n {n}", out.len()));
}
if row_ptr.len() != n + 1 {
return Err(format!(
"gmres_solve: row_ptr len {} != n+1 ({})",
row_ptr.len(),
n + 1
));
}
let m = max_iter.max(1) as usize;
let matvec = |x: &[f64], y: &mut [f64]| {
for r in 0..n {
let mut acc = 0f64;
for k in row_ptr[r] as usize..row_ptr[r + 1] as usize {
acc += values[k] * x[col_idx[k] as usize];
}
y[r] = acc;
}
};
let beta_init = b.iter().map(|v| v * v).sum::<f64>().sqrt();
if beta_init < tol {
for v in out.iter_mut() {
*v = 0.0;
}
return Ok(());
}
let mut v: Vec<Vec<f64>> = Vec::with_capacity(m + 1);
v.push(b.iter().map(|x| x / beta_init).collect());
let mut h: Vec<Vec<f64>> = Vec::with_capacity(m); let mut cs: Vec<f64> = Vec::with_capacity(m);
let mut sn: Vec<f64> = Vec::with_capacity(m);
let mut g: Vec<f64> = vec![0.0; m + 1];
g[0] = beta_init;
let mut converged_at: Option<usize> = None;
let mut w = vec![0f64; n];
for j in 0..m {
matvec(&v[j], &mut w);
let mut hcol = vec![0f64; j + 2];
for i in 0..=j {
hcol[i] = w.iter().zip(&v[i]).map(|(a, b)| a * b).sum();
for k in 0..n {
w[k] -= hcol[i] * v[i][k];
}
}
hcol[j + 1] = w.iter().map(|x| x * x).sum::<f64>().sqrt();
for i in 0..j {
let temp = cs[i] * hcol[i] + sn[i] * hcol[i + 1];
hcol[i + 1] = -sn[i] * hcol[i] + cs[i] * hcol[i + 1];
hcol[i] = temp;
}
let denom = (hcol[j] * hcol[j] + hcol[j + 1] * hcol[j + 1]).sqrt();
if denom == 0.0 {
return Err("gmres_solve: breakdown (denom = 0)".into());
}
let c = hcol[j] / denom;
let s = hcol[j + 1] / denom;
cs.push(c);
sn.push(s);
hcol[j] = c * hcol[j] + s * hcol[j + 1];
hcol[j + 1] = 0.0;
let g_temp = c * g[j] + s * g[j + 1];
g[j + 1] = -s * g[j] + c * g[j + 1];
g[j] = g_temp;
h.push(hcol);
if g[j + 1].abs() < tol {
converged_at = Some(j);
break;
}
if hcol_last_zero_check(&h[j]) {
converged_at = Some(j);
break;
}
if j + 1 < m {
let inv = 1.0 / hcol_subdiag(&h[j], j + 1).max(f64::MIN_POSITIVE);
let _ = inv;
let norm_w = w.iter().map(|x| x * x).sum::<f64>().sqrt();
if norm_w < f64::MIN_POSITIVE * 64.0 {
converged_at = Some(j);
break;
}
v.push(w.iter().map(|x| x / norm_w).collect());
}
}
let k = converged_at.map(|j| j + 1).unwrap_or(m);
let mut y = vec![0f64; k];
for i in (0..k).rev() {
let mut s = g[i];
for j in (i + 1)..k {
s -= h[j][i] * y[j];
}
y[i] = s / h[i][i];
}
for r in 0..n {
out[r] = 0.0;
}
for j in 0..k {
for r in 0..n {
out[r] += y[j] * v[j][r];
}
}
Ok(())
}
pub fn transpose_values(
values: &[f64],
col_idx: &[i32],
row_ptr: &[i32],
_col_idx_t: &[i32],
row_ptr_t: &[i32],
out: &mut [f64],
) -> Result<(), String> {
let n = row_ptr.len().saturating_sub(1);
let nnz = values.len();
if out.len() != nnz {
return Err(format!(
"transpose_values: out len {} != nnz {nnz}",
out.len()
));
}
let mut cursor: Vec<usize> = row_ptr_t.iter().map(|&x| x as usize).collect();
for r in 0..n {
let s = row_ptr[r] as usize;
let e = row_ptr[r + 1] as usize;
for k in s..e {
let c = col_idx[k] as usize;
let pos = cursor[c];
if pos >= nnz {
return Err(format!(
"transpose_values: cursor[{c}]={pos} ≥ nnz={nnz} \
(transposed pattern likely inconsistent with input)"
));
}
out[pos] = values[k];
cursor[c] += 1;
}
}
Ok(())
}
pub fn pcg_solve(
values: &[f64],
col_idx: &[i32],
row_ptr: &[i32],
b: &[f64],
out: &mut [f64],
max_iter: u32,
tol: f64,
) -> Result<(), String> {
let n = b.len();
if out.len() != n {
return Err(format!("pcg_solve: out len {} != n {n}", out.len()));
}
if row_ptr.len() != n + 1 {
return Err(format!(
"pcg_solve: row_ptr len {} != n+1 ({})",
row_ptr.len(),
n + 1
));
}
let mut diag = vec![1.0f64; n];
for r in 0..n {
for k in row_ptr[r] as usize..row_ptr[r + 1] as usize {
if col_idx[k] as usize == r {
diag[r] = values[k].max(f64::MIN_POSITIVE);
break;
}
}
}
let inv_diag: Vec<f64> = diag.iter().map(|&d| 1.0 / d).collect();
let matvec = |x: &[f64], y: &mut [f64]| {
for r in 0..n {
let mut acc = 0f64;
for k in row_ptr[r] as usize..row_ptr[r + 1] as usize {
acc += values[k] * x[col_idx[k] as usize];
}
y[r] = acc;
}
};
let mut x = vec![0f64; n];
let mut r = b.to_vec();
let mut z: Vec<f64> = r.iter().zip(&inv_diag).map(|(rv, mi)| rv * mi).collect();
let mut p = z.clone();
let mut ap = vec![0f64; n];
let mut rho_old: f64 = r.iter().zip(&z).map(|(a, b)| a * b).sum();
for _ in 0..max_iter {
let r_norm: f64 = r.iter().map(|v| v * v).sum::<f64>().sqrt();
if r_norm < tol {
break;
}
matvec(&p, &mut ap);
let pap: f64 = p.iter().zip(&ap).map(|(a, b)| a * b).sum();
if pap == 0.0 {
return Err("pcg_solve: pᵀ·A·p = 0 (A is singular or not SPD)".into());
}
let alpha = rho_old / pap;
for i in 0..n {
x[i] += alpha * p[i];
}
for i in 0..n {
r[i] -= alpha * ap[i];
}
for i in 0..n {
z[i] = r[i] * inv_diag[i];
}
let rho_new: f64 = r.iter().zip(&z).map(|(a, b)| a * b).sum();
let beta = rho_new / rho_old;
for i in 0..n {
p[i] = z[i] + beta * p[i];
}
rho_old = rho_new;
}
out.copy_from_slice(&x);
Ok(())
}
pub fn cholesky_solve(
values: &[f64],
col_idx: &[i32],
row_ptr: &[i32],
b: &[f64],
out: &mut [f64],
) -> Result<(), String> {
let n = b.len();
if out.len() != n {
return Err(format!("cholesky_solve: out len {} != n {n}", out.len()));
}
if row_ptr.len() != n + 1 {
return Err(format!(
"cholesky_solve: row_ptr len {} != n+1",
row_ptr.len()
));
}
let mut a_dense = vec![0f64; n * n];
for r in 0..n {
for k in row_ptr[r] as usize..row_ptr[r + 1] as usize {
a_dense[r * n + col_idx[k] as usize] = values[k];
}
}
let info = rlx_cpu::blas::dpotrf(&mut a_dense, n, true);
if info != 0 {
return Err(format!("cholesky_solve: dpotrf info={info} (not SPD?)"));
}
let mut x = b.to_vec();
rlx_cpu::blas::dtrsm_lower_or_upper(
&a_dense, &mut x, n, 1, true, false,
);
rlx_cpu::blas::dtrsm_lower_or_upper(
&a_dense, &mut x, n, 1, true, true,
);
out.copy_from_slice(&x);
Ok(())
}
pub fn bicgstab(
values: &[f64],
col_idx: &[i32],
row_ptr: &[i32],
b: &[f64],
out: &mut [f64],
max_iter: u32,
tol: f64,
transpose_a: bool,
) -> Result<(), String> {
let n = b.len();
if out.len() != n {
return Err(format!("bicgstab: out len {} != n {n}", out.len()));
}
if row_ptr.len() != n + 1 {
return Err(format!(
"bicgstab: row_ptr len {} != n+1 ({})",
row_ptr.len(),
n + 1
));
}
let matvec = |x: &[f64], y: &mut [f64]| {
if !transpose_a {
for r in 0..n {
let mut acc = 0f64;
for k in row_ptr[r] as usize..row_ptr[r + 1] as usize {
acc += values[k] * x[col_idx[k] as usize];
}
y[r] = acc;
}
} else {
for v in y.iter_mut() {
*v = 0.0;
}
for r in 0..n {
for k in row_ptr[r] as usize..row_ptr[r + 1] as usize {
y[col_idx[k] as usize] += values[k] * x[r];
}
}
}
};
let mut x = vec![0f64; n];
let mut r = b.to_vec();
let r_hat = r.clone();
let mut p = r.clone();
let mut v = vec![0f64; n];
let mut s = vec![0f64; n];
let mut t = vec![0f64; n];
let mut rho_old: f64 = r_hat.iter().zip(&r).map(|(a, b)| a * b).sum();
for _ in 0..max_iter {
let r_norm: f64 = r.iter().map(|v| v * v).sum::<f64>().sqrt();
if r_norm < tol {
break;
}
matvec(&p, &mut v);
let rh_v: f64 = r_hat.iter().zip(&v).map(|(a, b)| a * b).sum();
if rh_v == 0.0 {
return Err("bicgstab: breakdown r̂·v = 0".into());
}
let alpha = rho_old / rh_v;
for i in 0..n {
s[i] = r[i] - alpha * v[i];
}
let s_norm: f64 = s.iter().map(|v| v * v).sum::<f64>().sqrt();
if s_norm < tol {
for i in 0..n {
x[i] += alpha * p[i];
}
r[..n].copy_from_slice(&s[..n]);
break;
}
matvec(&s, &mut t);
let tt: f64 = t.iter().map(|v| v * v).sum();
if tt == 0.0 {
return Err("bicgstab: breakdown t·t = 0".into());
}
let ts: f64 = t.iter().zip(&s).map(|(a, b)| a * b).sum();
let omega = ts / tt;
for i in 0..n {
x[i] += alpha * p[i] + omega * s[i];
r[i] = s[i] - omega * t[i];
}
if omega == 0.0 {
return Err("bicgstab: ω = 0 (stagnation)".into());
}
let rho_new: f64 = r_hat.iter().zip(&r).map(|(a, b)| a * b).sum();
if rho_old == 0.0 {
return Err("bicgstab: ρ_old = 0".into());
}
let beta = (rho_new / rho_old) * (alpha / omega);
for i in 0..n {
p[i] = r[i] + beta * (p[i] - omega * v[i]);
}
rho_old = rho_new;
}
out.copy_from_slice(&x);
Ok(())
}
pub fn lsqr_solve(
values: &[f64],
col_idx: &[i32],
row_ptr: &[i32],
b: &[f64],
out: &mut [f64],
max_iter: u32,
tol: f64,
n_cols: usize,
) -> Result<(), String> {
let m = b.len();
let n = n_cols;
if out.len() != n {
return Err(format!("lsqr: out len {} != n {n}", out.len()));
}
if row_ptr.len() != m + 1 {
return Err(format!(
"lsqr: row_ptr len {} != m+1 ({})",
row_ptr.len(),
m + 1
));
}
let av = |x: &[f64], y: &mut [f64]| {
for r in 0..m {
let mut acc = 0f64;
for k in row_ptr[r] as usize..row_ptr[r + 1] as usize {
acc += values[k] * x[col_idx[k] as usize];
}
y[r] = acc;
}
};
let atv = |u: &[f64], y: &mut [f64]| {
for v in y.iter_mut() {
*v = 0.0;
}
for r in 0..m {
for k in row_ptr[r] as usize..row_ptr[r + 1] as usize {
y[col_idx[k] as usize] += values[k] * u[r];
}
}
};
let mut u = b.to_vec();
let mut beta: f64 = u.iter().map(|v| v * v).sum::<f64>().sqrt();
if beta == 0.0 {
for v in out.iter_mut() {
*v = 0.0;
}
return Ok(());
}
for v in u.iter_mut() {
*v /= beta;
}
let mut v = vec![0f64; n];
atv(&u, &mut v);
let mut alpha: f64 = v.iter().map(|x| x * x).sum::<f64>().sqrt();
if alpha == 0.0 {
for v in out.iter_mut() {
*v = 0.0;
}
return Ok(());
}
for x in v.iter_mut() {
*x /= alpha;
}
let mut x = vec![0f64; n];
let mut w = v.clone();
let mut phi_bar = beta;
let mut rho_bar = alpha;
let mut tmp_u = vec![0f64; m];
let mut tmp_v = vec![0f64; n];
for _ in 0..max_iter {
av(&v, &mut tmp_u);
for i in 0..m {
tmp_u[i] -= alpha * u[i];
}
beta = tmp_u.iter().map(|x| x * x).sum::<f64>().sqrt();
if beta != 0.0 {
for i in 0..m {
u[i] = tmp_u[i] / beta;
}
atv(&u, &mut tmp_v);
for i in 0..n {
tmp_v[i] -= beta * v[i];
}
alpha = tmp_v.iter().map(|x| x * x).sum::<f64>().sqrt();
if alpha != 0.0 {
for i in 0..n {
v[i] = tmp_v[i] / alpha;
}
}
}
let rho = (rho_bar * rho_bar + beta * beta).sqrt();
let c = rho_bar / rho;
let s = beta / rho;
let theta = s * alpha;
rho_bar = -c * alpha;
let phi = c * phi_bar;
phi_bar *= s;
let phi_over_rho = phi / rho;
let theta_over_rho = theta / rho;
for i in 0..n {
x[i] += phi_over_rho * w[i];
w[i] = v[i] - theta_over_rho * w[i];
}
if phi_bar.abs() < tol {
break;
}
if alpha == 0.0 || beta == 0.0 {
break;
}
}
out.copy_from_slice(&x);
Ok(())
}
pub fn ilu0_factor(
values: &[f64],
col_idx: &[i32],
row_ptr: &[i32],
n: usize,
out_fact: &mut [f64],
) -> Result<(), String> {
if out_fact.len() != values.len() {
return Err(format!(
"ilu0: out len {} != values len {}",
out_fact.len(),
values.len()
));
}
out_fact.copy_from_slice(values);
for i in 0..n {
let row_i_start = row_ptr[i] as usize;
let row_i_end = row_ptr[i + 1] as usize;
for k in row_i_start..row_i_end {
let j = col_idx[k] as usize;
if j >= i {
break;
}
let row_j_start = row_ptr[j] as usize;
let row_j_end = row_ptr[j + 1] as usize;
let mut a_jj = 0f64;
let mut found = false;
for kj in row_j_start..row_j_end {
if col_idx[kj] as usize == j {
a_jj = out_fact[kj];
found = true;
break;
}
}
if !found || a_jj == 0.0 {
return Err(format!("ilu0: zero/missing diag at row {j}"));
}
out_fact[k] /= a_jj;
let lij = out_fact[k];
for kk in (k + 1)..row_i_end {
let m = col_idx[kk] as usize;
for kj in row_j_start..row_j_end {
if col_idx[kj] as usize == m {
out_fact[kk] -= lij * out_fact[kj];
break;
}
}
}
}
}
Ok(())
}
pub fn ilu0_apply(
fact: &[f64],
col_idx: &[i32],
row_ptr: &[i32],
n: usize,
b: &[f64],
out: &mut [f64],
) {
for i in 0..n {
let mut acc = b[i];
for k in row_ptr[i] as usize..row_ptr[i + 1] as usize {
let j = col_idx[k] as usize;
if j < i {
acc -= fact[k] * out[j];
} else {
break;
}
}
out[i] = acc;
}
for i in (0..n).rev() {
let mut acc = out[i];
let mut diag = 1f64;
for k in row_ptr[i] as usize..row_ptr[i + 1] as usize {
let j = col_idx[k] as usize;
if j > i {
acc -= fact[k] * out[j];
} else if j == i {
diag = fact[k];
}
}
out[i] = acc / diag;
}
}
pub fn ilu_pcg_solve(
values: &[f64],
col_idx: &[i32],
row_ptr: &[i32],
b: &[f64],
out: &mut [f64],
max_iter: u32,
tol: f64,
) -> Result<(), String> {
let n = b.len();
if out.len() != n {
return Err(format!("ilu_pcg: out len {} != n {n}", out.len()));
}
let mut fact = vec![0f64; values.len()];
ilu0_factor(values, col_idx, row_ptr, n, &mut fact)?;
let matvec = |x: &[f64], y: &mut [f64]| {
for r in 0..n {
let mut acc = 0f64;
for k in row_ptr[r] as usize..row_ptr[r + 1] as usize {
acc += values[k] * x[col_idx[k] as usize];
}
y[r] = acc;
}
};
let mut x = vec![0f64; n];
let mut r = b.to_vec();
let mut z = vec![0f64; n];
ilu0_apply(&fact, col_idx, row_ptr, n, &r, &mut z);
let mut p = z.clone();
let mut ap = vec![0f64; n];
let mut rho_old: f64 = r.iter().zip(&z).map(|(a, b)| a * b).sum();
for _ in 0..max_iter {
let r_norm: f64 = r.iter().map(|v| v * v).sum::<f64>().sqrt();
if r_norm < tol {
break;
}
matvec(&p, &mut ap);
let pap: f64 = p.iter().zip(&ap).map(|(a, b)| a * b).sum();
if pap == 0.0 {
return Err("ilu_pcg: pᵀ·A·p = 0".into());
}
let alpha = rho_old / pap;
for i in 0..n {
x[i] += alpha * p[i];
}
for i in 0..n {
r[i] -= alpha * ap[i];
}
ilu0_apply(&fact, col_idx, row_ptr, n, &r, &mut z);
let rho_new: f64 = r.iter().zip(&z).map(|(a, b)| a * b).sum();
let beta = rho_new / rho_old;
for i in 0..n {
p[i] = z[i] + beta * p[i];
}
rho_old = rho_new;
}
out.copy_from_slice(&x);
Ok(())
}
pub fn spgemm_csr(
a_values: &[f64],
a_col_idx: &[i32],
a_row_ptr: &[i32],
b_values: &[f64],
b_col_idx: &[i32],
b_row_ptr: &[i32],
m: usize,
k: usize,
n: usize,
) -> Result<(Vec<f64>, Vec<i32>, Vec<i32>), String> {
if a_row_ptr.len() != m + 1 {
return Err(format!("spgemm: a_row_ptr len {} != m+1", a_row_ptr.len()));
}
if b_row_ptr.len() != k + 1 {
return Err(format!("spgemm: b_row_ptr len {} != k+1", b_row_ptr.len()));
}
let mut c_row_ptr = vec![0i32; m + 1];
let mut c_col_idx: Vec<i32> = Vec::new();
let mut c_values: Vec<f64> = Vec::new();
let mut marker = vec![-1i32; n];
let mut spa_vals = vec![0f64; n];
let mut spa_cols: Vec<usize> = Vec::with_capacity(n);
for i in 0..m {
spa_cols.clear();
for ka in a_row_ptr[i] as usize..a_row_ptr[i + 1] as usize {
let j = a_col_idx[ka] as usize;
let aij = a_values[ka];
for kb in b_row_ptr[j] as usize..b_row_ptr[j + 1] as usize {
let l = b_col_idx[kb] as usize;
let bjl = b_values[kb];
if marker[l] != i as i32 {
marker[l] = i as i32;
spa_vals[l] = aij * bjl;
spa_cols.push(l);
} else {
spa_vals[l] += aij * bjl;
}
}
}
spa_cols.sort_unstable();
for &l in &spa_cols {
c_col_idx.push(l as i32);
c_values.push(spa_vals[l]);
}
c_row_ptr[i + 1] = c_col_idx.len() as i32;
}
Ok((c_values, c_col_idx, c_row_ptr))
}
fn hcol_last_zero_check(hcol: &[f64]) -> bool {
hcol.iter().all(|v| v.abs() < f64::MIN_POSITIVE * 64.0)
}
fn hcol_subdiag(hcol: &[f64], i: usize) -> f64 {
hcol.get(i).copied().unwrap_or(0.0)
}
pub fn cg_solve(
values: &[f64],
col_idx: &[i32],
row_ptr: &[i32],
b: &[f64],
out: &mut [f64],
max_iter: u32,
tol: f64,
) -> Result<(), String> {
let n = b.len();
if out.len() != n {
return Err(format!("cg_solve: output len {} != b len {n}", out.len()));
}
if row_ptr.len() != n + 1 {
return Err(format!(
"cg_solve: row_ptr len {} != n+1 ({})",
row_ptr.len(),
n + 1
));
}
let matvec = |x: &[f64], y: &mut [f64]| {
for r in 0..n {
let mut acc = 0f64;
for k in row_ptr[r] as usize..row_ptr[r + 1] as usize {
acc += values[k] * x[col_idx[k] as usize];
}
y[r] = acc;
}
};
let mut x = vec![0f64; n];
let mut r = b.to_vec();
let mut p = r.clone();
let mut ap = vec![0f64; n];
let mut rs_old: f64 = r.iter().map(|v| v * v).sum();
for _ in 0..max_iter {
if rs_old.sqrt() < tol {
break;
}
matvec(&p, &mut ap);
let pap: f64 = p.iter().zip(&ap).map(|(a, b)| a * b).sum();
if pap == 0.0 {
return Err("cg_solve: pᵀ·A·p = 0 (A is singular or not SPD)".into());
}
let alpha = rs_old / pap;
for i in 0..n {
x[i] += alpha * p[i];
}
for i in 0..n {
r[i] -= alpha * ap[i];
}
let rs_new: f64 = r.iter().map(|v| v * v).sum();
let beta = rs_new / rs_old;
for i in 0..n {
p[i] = r[i] + beta * p[i];
}
rs_old = rs_new;
}
out.copy_from_slice(&x);
Ok(())
}
}
pub fn encode_cg_attrs(max_iter: u32, tol: f64) -> Vec<u8> {
let mut out = Vec::with_capacity(12);
out.extend_from_slice(&max_iter.to_le_bytes());
out.extend_from_slice(&tol.to_le_bytes());
out
}
#[cfg(feature = "cpu")]
pub fn spgemm_csr(
a_values: &[f64],
a_col_idx: &[i32],
a_row_ptr: &[i32],
b_values: &[f64],
b_col_idx: &[i32],
b_row_ptr: &[i32],
m: usize,
k: usize,
n: usize,
) -> Result<(Vec<f64>, Vec<i32>, Vec<i32>), String> {
algos::spgemm_csr(
a_values, a_col_idx, a_row_ptr, b_values, b_col_idx, b_row_ptr, m, k, n,
)
}
pub fn csr_transpose_pattern(
col_idx: &[i32],
row_ptr: &[i32],
n_rows: usize,
n_cols: usize,
) -> (Vec<i32>, Vec<i32>) {
let nnz = col_idx.len();
let mut t_count = vec![0i32; n_cols];
for &c in col_idx {
t_count[c as usize] += 1;
}
let mut t_row_ptr = vec![0i32; n_cols + 1];
for r in 0..n_cols {
t_row_ptr[r + 1] = t_row_ptr[r] + t_count[r];
}
let mut t_col_idx = vec![0i32; nnz];
let mut cursor = t_row_ptr.clone();
for r in 0..n_rows {
for k in row_ptr[r] as usize..row_ptr[r + 1] as usize {
let c = col_idx[k] as usize;
let pos = cursor[c] as usize;
t_col_idx[pos] = r as i32;
cursor[c] += 1;
}
}
(t_col_idx, t_row_ptr)
}
#[derive(Clone, Copy, Debug)]
pub struct SparseTensor {
pub values: NodeId,
pub col_idx: NodeId,
pub row_ptr: NodeId,
pub n_rows: usize,
pub n_cols: usize,
}
impl SparseTensor {
pub fn from_csr(
values: NodeId,
col_idx: NodeId,
row_ptr: NodeId,
n_rows: usize,
n_cols: usize,
) -> Self {
Self {
values,
col_idx,
row_ptr,
n_rows,
n_cols,
}
}
pub fn mat_vec(&self, g: &mut Graph, x: NodeId) -> NodeId {
g.custom_op(
SPARSE_MAT_VEC,
Vec::new(),
vec![self.values, self.col_idx, self.row_ptr, x],
)
}
pub fn solve(&self, g: &mut Graph, b: NodeId) -> NodeId {
assert_eq!(
self.n_rows, self.n_cols,
"SparseTensor::solve requires a square matrix"
);
g.custom_op(
SPARSE_LU_SOLVE,
Vec::new(),
vec![self.values, self.col_idx, self.row_ptr, b],
)
}
pub fn cg_solve(&self, g: &mut Graph, b: NodeId, max_iter: u32, tol: f64) -> NodeId {
assert_eq!(
self.n_rows, self.n_cols,
"SparseTensor::cg_solve requires a square matrix"
);
g.custom_op(
SPARSE_CG_SOLVE,
encode_cg_attrs(max_iter, tol),
vec![self.values, self.col_idx, self.row_ptr, b],
)
}
pub fn solve_general(&self, g: &mut Graph, b: NodeId, adjoint: &SparseTensor) -> NodeId {
assert_eq!(
self.n_rows, self.n_cols,
"SparseTensor::solve_general requires a square matrix"
);
assert_eq!(
adjoint.n_rows, self.n_cols,
"adjoint shape mismatch: A is {}×{}, Aᵀ should be {}×{}",
self.n_rows, self.n_cols, self.n_cols, self.n_rows
);
g.custom_op(
SPARSE_LU_SOLVE_GENERAL,
Vec::new(),
vec![
self.values,
self.col_idx,
self.row_ptr,
b,
adjoint.values,
adjoint.col_idx,
adjoint.row_ptr,
],
)
}
pub fn pcg_solve(&self, g: &mut Graph, b: NodeId, max_iter: u32, tol: f64) -> NodeId {
assert_eq!(
self.n_rows, self.n_cols,
"SparseTensor::pcg_solve requires a square matrix"
);
g.custom_op(
SPARSE_PCG_SOLVE,
encode_cg_attrs(max_iter, tol),
vec![self.values, self.col_idx, self.row_ptr, b],
)
}
pub fn transpose_values(&self, g: &mut Graph, col_idx_t: NodeId, row_ptr_t: NodeId) -> NodeId {
g.custom_op(
SPARSE_TRANSPOSE_VALUES,
Vec::new(),
vec![
self.values,
self.col_idx,
self.row_ptr,
col_idx_t,
row_ptr_t,
],
)
}
pub fn cholesky_solve(&self, g: &mut Graph, b: NodeId) -> NodeId {
assert_eq!(
self.n_rows, self.n_cols,
"SparseTensor::cholesky_solve requires a square matrix"
);
g.custom_op(
SPARSE_CHOLESKY_SOLVE,
Vec::new(),
vec![self.values, self.col_idx, self.row_ptr, b],
)
}
pub fn lsqr_solve(&self, g: &mut Graph, b: NodeId, max_iter: u32, tol: f64) -> NodeId {
let mut attrs = Vec::with_capacity(16);
attrs.extend_from_slice(&max_iter.to_le_bytes());
attrs.extend_from_slice(&tol.to_le_bytes());
attrs.extend_from_slice(&(self.n_cols as u32).to_le_bytes());
g.custom_op(
SPARSE_LSQR_SOLVE,
attrs,
vec![self.values, self.col_idx, self.row_ptr, b],
)
}
pub fn bicgstab_solve(&self, g: &mut Graph, b: NodeId, max_iter: u32, tol: f64) -> NodeId {
assert_eq!(
self.n_rows, self.n_cols,
"SparseTensor::bicgstab_solve requires a square matrix"
);
let mut attrs = Vec::with_capacity(13);
attrs.extend_from_slice(&max_iter.to_le_bytes());
attrs.extend_from_slice(&tol.to_le_bytes());
attrs.push(0); g.custom_op(
SPARSE_BICGSTAB_SOLVE,
attrs,
vec![self.values, self.col_idx, self.row_ptr, b],
)
}
pub fn ilu_pcg_solve(&self, g: &mut Graph, b: NodeId, max_iter: u32, tol: f64) -> NodeId {
assert_eq!(
self.n_rows, self.n_cols,
"SparseTensor::ilu_pcg_solve requires a square matrix"
);
g.custom_op(
SPARSE_ILU_PCG_SOLVE,
encode_cg_attrs(max_iter, tol),
vec![self.values, self.col_idx, self.row_ptr, b],
)
}
pub fn gmres_solve(
&self,
g: &mut Graph,
b: NodeId,
max_iter: u32,
tol: f64,
adjoint: &SparseTensor,
) -> NodeId {
assert_eq!(
self.n_rows, self.n_cols,
"SparseTensor::gmres_solve requires a square matrix"
);
assert_eq!(adjoint.n_rows, self.n_cols, "adjoint shape mismatch");
g.custom_op(
SPARSE_GMRES_SOLVE,
encode_cg_attrs(max_iter, tol),
vec![
self.values,
self.col_idx,
self.row_ptr,
b,
adjoint.values,
adjoint.col_idx,
adjoint.row_ptr,
],
)
}
}
#[cfg(all(feature = "metal", target_vendor = "apple", not(target_os = "watchos")))]
mod metal_kernels {
use super::*;
use rlx_ir::DType;
use rlx_metal::op_registry::MetalKernel;
unsafe fn typed<'a, T: Copy>(
bytes: &'a [u8],
shape: &rlx_ir::Shape,
want: DType,
role: &str,
) -> Result<&'a [T], String> {
if shape.dtype() != want {
return Err(format!(
"{role}: expected {want:?}, got {:?}",
shape.dtype()
));
}
let n = shape
.num_elements()
.ok_or_else(|| format!("{role}: dynamic shape not supported"))?;
let need = n * std::mem::size_of::<T>();
if bytes.len() < need {
return Err(format!("{role}: bytes {} < need {need}", bytes.len()));
}
Ok(unsafe { std::slice::from_raw_parts(bytes.as_ptr() as *const T, n) })
}
unsafe fn typed_mut<'a, T: Copy>(
bytes: &'a mut [u8],
shape: &rlx_ir::Shape,
want: DType,
role: &str,
) -> Result<&'a mut [T], String> {
if shape.dtype() != want {
return Err(format!(
"{role}: expected {want:?}, got {:?}",
shape.dtype()
));
}
let n = shape
.num_elements()
.ok_or_else(|| format!("{role}: dynamic shape not supported"))?;
let need = n * std::mem::size_of::<T>();
if bytes.len() < need {
return Err(format!("{role}: bytes {} < need {need}", bytes.len()));
}
Ok(unsafe { std::slice::from_raw_parts_mut(bytes.as_mut_ptr() as *mut T, n) })
}
#[derive(Debug)]
pub(super) struct SparseLuMetal;
impl MetalKernel for SparseLuMetal {
fn name(&self) -> &str {
SPARSE_LU_SOLVE
}
fn execute(
&self,
inputs: &[(&[u8], &rlx_ir::Shape)],
output: (&mut [u8], &rlx_ir::Shape),
_attrs: &[u8],
) -> Result<(), String> {
unsafe {
let values = typed::<f64>(inputs[0].0, inputs[0].1, DType::F64, "values")?;
let col_idx = typed::<i32>(inputs[1].0, inputs[1].1, DType::I32, "col_idx")?;
let row_ptr = typed::<i32>(inputs[2].0, inputs[2].1, DType::I32, "row_ptr")?;
let b = typed::<f64>(inputs[3].0, inputs[3].1, DType::F64, "b")?;
let out = typed_mut::<f64>(output.0, output.1, DType::F64, "out")?;
algos::lu_solve(values, col_idx, row_ptr, b, out)
}
}
}
#[derive(Debug)]
pub(super) struct SparseMatVecMetal;
impl MetalKernel for SparseMatVecMetal {
fn name(&self) -> &str {
SPARSE_MAT_VEC
}
fn execute(
&self,
inputs: &[(&[u8], &rlx_ir::Shape)],
output: (&mut [u8], &rlx_ir::Shape),
_attrs: &[u8],
) -> Result<(), String> {
unsafe {
let values = typed::<f64>(inputs[0].0, inputs[0].1, DType::F64, "values")?;
let col_idx = typed::<i32>(inputs[1].0, inputs[1].1, DType::I32, "col_idx")?;
let row_ptr = typed::<i32>(inputs[2].0, inputs[2].1, DType::I32, "row_ptr")?;
let x = typed::<f64>(inputs[3].0, inputs[3].1, DType::F64, "x")?;
let out = typed_mut::<f64>(output.0, output.1, DType::F64, "out")?;
algos::mat_vec(values, col_idx, row_ptr, x, out)
}
}
}
#[derive(Debug)]
pub(super) struct SparseCgMetal;
impl MetalKernel for SparseCgMetal {
fn name(&self) -> &str {
SPARSE_CG_SOLVE
}
fn execute(
&self,
inputs: &[(&[u8], &rlx_ir::Shape)],
output: (&mut [u8], &rlx_ir::Shape),
attrs: &[u8],
) -> Result<(), String> {
let (max_iter, tol) = decode_cg_attrs(attrs)?;
unsafe {
let values = typed::<f64>(inputs[0].0, inputs[0].1, DType::F64, "values")?;
let col_idx = typed::<i32>(inputs[1].0, inputs[1].1, DType::I32, "col_idx")?;
let row_ptr = typed::<i32>(inputs[2].0, inputs[2].1, DType::I32, "row_ptr")?;
let b = typed::<f64>(inputs[3].0, inputs[3].1, DType::F64, "b")?;
let out = typed_mut::<f64>(output.0, output.1, DType::F64, "out")?;
algos::cg_solve(values, col_idx, row_ptr, b, out, max_iter, tol)
}
}
}
#[derive(Debug)]
pub(super) struct SparseValuesGradMetal;
impl MetalKernel for SparseValuesGradMetal {
fn name(&self) -> &str {
SPARSE_VALUES_GRAD
}
fn execute(
&self,
inputs: &[(&[u8], &rlx_ir::Shape)],
output: (&mut [u8], &rlx_ir::Shape),
_attrs: &[u8],
) -> Result<(), String> {
unsafe {
let col_idx = typed::<i32>(inputs[0].0, inputs[0].1, DType::I32, "col_idx")?;
let row_ptr = typed::<i32>(inputs[1].0, inputs[1].1, DType::I32, "row_ptr")?;
let u = typed::<f64>(inputs[2].0, inputs[2].1, DType::F64, "u")?;
let v = typed::<f64>(inputs[3].0, inputs[3].1, DType::F64, "v")?;
let out = typed_mut::<f64>(output.0, output.1, DType::F64, "out")?;
algos::values_grad(col_idx, row_ptr, u, v, out)
}
}
}
#[derive(Debug)]
pub(super) struct SparseLuGeneralMetal;
impl MetalKernel for SparseLuGeneralMetal {
fn name(&self) -> &str {
SPARSE_LU_SOLVE_GENERAL
}
fn execute(
&self,
inputs: &[(&[u8], &rlx_ir::Shape)],
output: (&mut [u8], &rlx_ir::Shape),
_attrs: &[u8],
) -> Result<(), String> {
unsafe {
let values = typed::<f64>(inputs[0].0, inputs[0].1, DType::F64, "values")?;
let col_idx = typed::<i32>(inputs[1].0, inputs[1].1, DType::I32, "col_idx")?;
let row_ptr = typed::<i32>(inputs[2].0, inputs[2].1, DType::I32, "row_ptr")?;
let b = typed::<f64>(inputs[3].0, inputs[3].1, DType::F64, "b")?;
let out = typed_mut::<f64>(output.0, output.1, DType::F64, "out")?;
algos::lu_solve(values, col_idx, row_ptr, b, out)
}
}
}
#[derive(Debug)]
pub(super) struct SparseGmresMetal;
impl MetalKernel for SparseGmresMetal {
fn name(&self) -> &str {
SPARSE_GMRES_SOLVE
}
fn execute(
&self,
inputs: &[(&[u8], &rlx_ir::Shape)],
output: (&mut [u8], &rlx_ir::Shape),
attrs: &[u8],
) -> Result<(), String> {
let (max_iter, tol) = decode_cg_attrs(attrs)?;
unsafe {
let values = typed::<f64>(inputs[0].0, inputs[0].1, DType::F64, "values")?;
let col_idx = typed::<i32>(inputs[1].0, inputs[1].1, DType::I32, "col_idx")?;
let row_ptr = typed::<i32>(inputs[2].0, inputs[2].1, DType::I32, "row_ptr")?;
let b = typed::<f64>(inputs[3].0, inputs[3].1, DType::F64, "b")?;
let out = typed_mut::<f64>(output.0, output.1, DType::F64, "out")?;
algos::gmres_solve(values, col_idx, row_ptr, b, out, max_iter, tol)
}
}
}
}
#[cfg(all(feature = "mlx", target_os = "macos"))]
mod mlx_kernels {
use super::*;
use rlx_ir::DType;
use rlx_mlx::array::{Array, MlxError};
use rlx_mlx::op_registry::MlxKernel;
fn shape_dims_static(s: &rlx_ir::Shape) -> Result<Vec<usize>, MlxError> {
s.dims()
.iter()
.map(|d| match d {
rlx_ir::Dim::Static(n) => Ok(*n),
_ => Err(MlxError(
"rlx-sparse mlx kernel: dynamic shape not supported".into(),
)),
})
.collect()
}
fn bytes_to_f64(b: &[u8]) -> Vec<f64> {
b.chunks_exact(8)
.map(|c| f64::from_le_bytes(c.try_into().unwrap()))
.collect()
}
fn bytes_to_i32(b: &[u8]) -> Vec<i32> {
b.chunks_exact(4)
.map(|c| i32::from_le_bytes(c.try_into().unwrap()))
.collect()
}
fn f64_to_bytes(xs: &[f64]) -> Vec<u8> {
let mut out = Vec::with_capacity(xs.len() * 8);
for x in xs {
out.extend_from_slice(&x.to_le_bytes());
}
out
}
fn run_lu(inputs: &[&Array], output_shape: &rlx_ir::Shape) -> Result<Array, MlxError> {
let values = bytes_to_f64(&inputs[0].to_bytes()?);
let col_idx = bytes_to_i32(&inputs[1].to_bytes()?);
let row_ptr = bytes_to_i32(&inputs[2].to_bytes()?);
let b = bytes_to_f64(&inputs[3].to_bytes()?);
let mut out = vec![0f64; b.len()];
algos::lu_solve(&values, &col_idx, &row_ptr, &b, &mut out).map_err(MlxError)?;
let dims = shape_dims_static(output_shape)?;
Array::from_bytes(&f64_to_bytes(&out), &dims, DType::F64)
}
fn run_mat_vec(inputs: &[&Array], output_shape: &rlx_ir::Shape) -> Result<Array, MlxError> {
let values = bytes_to_f64(&inputs[0].to_bytes()?);
let col_idx = bytes_to_i32(&inputs[1].to_bytes()?);
let row_ptr = bytes_to_i32(&inputs[2].to_bytes()?);
let x = bytes_to_f64(&inputs[3].to_bytes()?);
let mut out = vec![0f64; x.len()];
algos::mat_vec(&values, &col_idx, &row_ptr, &x, &mut out).map_err(MlxError)?;
let dims = shape_dims_static(output_shape)?;
Array::from_bytes(&f64_to_bytes(&out), &dims, DType::F64)
}
fn run_cg(
inputs: &[&Array],
output_shape: &rlx_ir::Shape,
attrs: &[u8],
) -> Result<Array, MlxError> {
let (max_iter, tol) = decode_cg_attrs(attrs).map_err(MlxError)?;
let values = bytes_to_f64(&inputs[0].to_bytes()?);
let col_idx = bytes_to_i32(&inputs[1].to_bytes()?);
let row_ptr = bytes_to_i32(&inputs[2].to_bytes()?);
let b = bytes_to_f64(&inputs[3].to_bytes()?);
let mut out = vec![0f64; b.len()];
algos::cg_solve(&values, &col_idx, &row_ptr, &b, &mut out, max_iter, tol)
.map_err(MlxError)?;
let dims = shape_dims_static(output_shape)?;
Array::from_bytes(&f64_to_bytes(&out), &dims, DType::F64)
}
pub(super) struct SparseLuMlx;
impl MlxKernel for SparseLuMlx {
fn name(&self) -> &str {
SPARSE_LU_SOLVE
}
fn execute(
&self,
inputs: &[&Array],
out_shape: &rlx_ir::Shape,
_attrs: &[u8],
) -> Result<Array, MlxError> {
run_lu(inputs, out_shape)
}
}
pub(super) struct SparseMatVecMlx;
impl MlxKernel for SparseMatVecMlx {
fn name(&self) -> &str {
SPARSE_MAT_VEC
}
fn execute(
&self,
inputs: &[&Array],
out_shape: &rlx_ir::Shape,
_attrs: &[u8],
) -> Result<Array, MlxError> {
run_mat_vec(inputs, out_shape)
}
}
pub(super) struct SparseCgMlx;
impl MlxKernel for SparseCgMlx {
fn name(&self) -> &str {
SPARSE_CG_SOLVE
}
fn execute(
&self,
inputs: &[&Array],
out_shape: &rlx_ir::Shape,
attrs: &[u8],
) -> Result<Array, MlxError> {
run_cg(inputs, out_shape, attrs)
}
}
fn run_values_grad(inputs: &[&Array], output_shape: &rlx_ir::Shape) -> Result<Array, MlxError> {
let col_idx = bytes_to_i32(&inputs[0].to_bytes()?);
let row_ptr = bytes_to_i32(&inputs[1].to_bytes()?);
let u = bytes_to_f64(&inputs[2].to_bytes()?);
let v = bytes_to_f64(&inputs[3].to_bytes()?);
let mut out = vec![0f64; col_idx.len()];
algos::values_grad(&col_idx, &row_ptr, &u, &v, &mut out).map_err(MlxError)?;
let dims = shape_dims_static(output_shape)?;
Array::from_bytes(&f64_to_bytes(&out), &dims, DType::F64)
}
fn run_lu_general(inputs: &[&Array], output_shape: &rlx_ir::Shape) -> Result<Array, MlxError> {
let values = bytes_to_f64(&inputs[0].to_bytes()?);
let col_idx = bytes_to_i32(&inputs[1].to_bytes()?);
let row_ptr = bytes_to_i32(&inputs[2].to_bytes()?);
let b = bytes_to_f64(&inputs[3].to_bytes()?);
let mut out = vec![0f64; b.len()];
algos::lu_solve(&values, &col_idx, &row_ptr, &b, &mut out).map_err(MlxError)?;
let dims = shape_dims_static(output_shape)?;
Array::from_bytes(&f64_to_bytes(&out), &dims, DType::F64)
}
fn run_gmres(
inputs: &[&Array],
output_shape: &rlx_ir::Shape,
attrs: &[u8],
) -> Result<Array, MlxError> {
let (max_iter, tol) = decode_cg_attrs(attrs).map_err(MlxError)?;
let values = bytes_to_f64(&inputs[0].to_bytes()?);
let col_idx = bytes_to_i32(&inputs[1].to_bytes()?);
let row_ptr = bytes_to_i32(&inputs[2].to_bytes()?);
let b = bytes_to_f64(&inputs[3].to_bytes()?);
let mut out = vec![0f64; b.len()];
algos::gmres_solve(&values, &col_idx, &row_ptr, &b, &mut out, max_iter, tol)
.map_err(MlxError)?;
let dims = shape_dims_static(output_shape)?;
Array::from_bytes(&f64_to_bytes(&out), &dims, DType::F64)
}
pub(super) struct SparseValuesGradMlx;
impl MlxKernel for SparseValuesGradMlx {
fn name(&self) -> &str {
SPARSE_VALUES_GRAD
}
fn execute(
&self,
inputs: &[&Array],
out_shape: &rlx_ir::Shape,
_attrs: &[u8],
) -> Result<Array, MlxError> {
run_values_grad(inputs, out_shape)
}
}
pub(super) struct SparseLuGeneralMlx;
impl MlxKernel for SparseLuGeneralMlx {
fn name(&self) -> &str {
SPARSE_LU_SOLVE_GENERAL
}
fn execute(
&self,
inputs: &[&Array],
out_shape: &rlx_ir::Shape,
_attrs: &[u8],
) -> Result<Array, MlxError> {
run_lu_general(inputs, out_shape)
}
}
pub(super) struct SparseGmresMlx;
impl MlxKernel for SparseGmresMlx {
fn name(&self) -> &str {
SPARSE_GMRES_SOLVE
}
fn execute(
&self,
inputs: &[&Array],
out_shape: &rlx_ir::Shape,
attrs: &[u8],
) -> Result<Array, MlxError> {
run_gmres(inputs, out_shape, attrs)
}
}
}
pub fn cg_solve(
values: &[f64],
col_idx: &[i32],
row_ptr: &[i32],
b: &[f64],
out: &mut [f64],
max_iter: u32,
tol: f64,
) -> Result<(), String> {
algos::cg_solve(values, col_idx, row_ptr, b, out, max_iter, tol)
}
pub fn register() {
register_op(Arc::new(SparseLuExt));
register_op(Arc::new(SparseMatVecExt));
register_op(Arc::new(SparseCgExt));
register_op(Arc::new(SparseValuesGradExt));
register_op(Arc::new(SparseLuGeneralExt));
register_op(Arc::new(SparseGmresExt));
register_op(Arc::new(SparseTransposeValuesExt));
register_op(Arc::new(SparsePcgExt));
register_op(Arc::new(SparseBicgstabExt));
register_op(Arc::new(SparseIluPcgExt));
register_op(Arc::new(SparseCholeskyExt));
register_op(Arc::new(SparseLsqrExt));
#[cfg(feature = "cpu")]
{
register_cpu_kernel(Arc::new(SparseLuCpu));
register_cpu_kernel(Arc::new(SparseMatVecCpu));
register_cpu_kernel(Arc::new(SparseCgCpu));
register_cpu_kernel(Arc::new(SparseValuesGradCpu));
register_cpu_kernel(Arc::new(SparseLuGeneralCpu));
register_cpu_kernel(Arc::new(SparseGmresCpu));
register_cpu_kernel(Arc::new(SparseTransposeValuesCpu));
register_cpu_kernel(Arc::new(SparsePcgCpu));
register_cpu_kernel(Arc::new(SparseBicgstabCpu));
register_cpu_kernel(Arc::new(SparseIluPcgCpu));
register_cpu_kernel(Arc::new(SparseCholeskyCpu));
register_cpu_kernel(Arc::new(SparseLsqrCpu));
}
#[cfg(all(feature = "metal", target_vendor = "apple", not(target_os = "watchos")))]
{
use rlx_metal::op_registry::register_metal_kernel;
register_metal_kernel(Arc::new(metal_kernels::SparseLuMetal));
register_metal_kernel(Arc::new(metal_kernels::SparseMatVecMetal));
register_metal_kernel(Arc::new(metal_kernels::SparseCgMetal));
register_metal_kernel(Arc::new(metal_kernels::SparseValuesGradMetal));
register_metal_kernel(Arc::new(metal_kernels::SparseLuGeneralMetal));
register_metal_kernel(Arc::new(metal_kernels::SparseGmresMetal));
}
#[cfg(all(feature = "mlx", target_os = "macos"))]
{
use rlx_mlx::op_registry::register_mlx_kernel;
register_mlx_kernel(Arc::new(mlx_kernels::SparseLuMlx));
register_mlx_kernel(Arc::new(mlx_kernels::SparseMatVecMlx));
register_mlx_kernel(Arc::new(mlx_kernels::SparseCgMlx));
register_mlx_kernel(Arc::new(mlx_kernels::SparseValuesGradMlx));
register_mlx_kernel(Arc::new(mlx_kernels::SparseLuGeneralMlx));
register_mlx_kernel(Arc::new(mlx_kernels::SparseGmresMlx));
}
}