use std::mem;
use oxicuda_blas::GpuFloat;
use crate::error::{SparseError, SparseResult};
use crate::format::CsrMatrix;
pub const HYB_ELL_SENTINEL: i32 = -1;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum HybPartition {
Auto,
Max,
Threshold(f64),
Fixed(usize),
}
#[derive(Debug, Clone)]
pub struct HybMatrix<T: GpuFloat> {
rows: usize,
cols: usize,
ell_width: usize,
ell_col_indices: Vec<i32>,
ell_values: Vec<T>,
coo_nnz: usize,
coo_row_indices: Vec<i32>,
coo_col_indices: Vec<i32>,
coo_values: Vec<T>,
}
#[derive(Debug, Clone, Copy)]
pub struct HybStatistics {
pub ell_fraction: f64,
pub coo_fraction: f64,
pub ell_padding_ratio: f64,
pub memory_bytes: usize,
pub csr_memory_bytes: usize,
}
pub fn optimal_ell_width<T: GpuFloat>(row_nnz: &[usize]) -> usize {
if row_nnz.is_empty() {
return 1;
}
let rows = row_nnz.len();
let max_nnz = row_nnz.iter().copied().max().unwrap_or(1);
if max_nnz == 0 {
return 1;
}
let ell_entry_bytes = mem::size_of::<i32>() + mem::size_of::<T>();
let coo_entry_bytes = 2 * mem::size_of::<i32>() + mem::size_of::<T>();
let mut best_width = 1;
let mut best_cost = usize::MAX;
for w in 1..=max_nnz {
let ell_cost = rows * w * ell_entry_bytes;
let overflow: usize = row_nnz.iter().map(|&r| r.saturating_sub(w)).sum();
let coo_cost = overflow * coo_entry_bytes;
let total = ell_cost + coo_cost;
if total < best_cost {
best_cost = total;
best_width = w;
}
}
best_width
}
fn compute_ell_width(row_nnz: &[usize], partition: HybPartition) -> usize {
if row_nnz.is_empty() {
return 1;
}
match partition {
HybPartition::Auto => {
let mut sorted = row_nnz.to_vec();
sorted.sort_unstable();
let mid = sorted.len() / 2;
let median = if sorted.len() % 2 == 0 {
(sorted[mid.saturating_sub(1)] + sorted[mid]) / 2
} else {
sorted[mid]
};
median.max(1)
}
HybPartition::Max => row_nnz.iter().copied().max().unwrap_or(1).max(1),
HybPartition::Threshold(pct) => {
let pct = pct.clamp(0.0, 1.0);
let mut sorted = row_nnz.to_vec();
sorted.sort_unstable();
let idx = ((sorted.len() as f64 * pct).ceil() as usize)
.min(sorted.len())
.saturating_sub(1);
sorted[idx].max(1)
}
HybPartition::Fixed(w) => w.max(1),
}
}
impl<T: GpuFloat> HybMatrix<T> {
#[allow(clippy::too_many_arguments)]
pub fn new(
rows: usize,
cols: usize,
ell_width: usize,
ell_col_indices: Vec<i32>,
ell_values: Vec<T>,
coo_row_indices: Vec<i32>,
coo_col_indices: Vec<i32>,
coo_values: Vec<T>,
) -> SparseResult<Self> {
if rows == 0 || cols == 0 {
return Err(SparseError::InvalidFormat(
"rows and cols must be non-zero".to_string(),
));
}
if ell_width == 0 {
return Err(SparseError::InvalidFormat(
"ell_width must be non-zero".to_string(),
));
}
let ell_total = rows * ell_width;
if ell_col_indices.len() != ell_total {
return Err(SparseError::InvalidFormat(format!(
"ell_col_indices length ({}) must be rows * ell_width ({})",
ell_col_indices.len(),
ell_total
)));
}
if ell_values.len() != ell_total {
return Err(SparseError::InvalidFormat(format!(
"ell_values length ({}) must be rows * ell_width ({})",
ell_values.len(),
ell_total
)));
}
let coo_nnz = coo_values.len();
if coo_row_indices.len() != coo_nnz || coo_col_indices.len() != coo_nnz {
return Err(SparseError::InvalidFormat(format!(
"COO arrays must have equal length: row_indices={}, col_indices={}, values={}",
coo_row_indices.len(),
coo_col_indices.len(),
coo_nnz
)));
}
Ok(Self {
rows,
cols,
ell_width,
ell_col_indices,
ell_values,
coo_nnz,
coo_row_indices,
coo_col_indices,
coo_values,
})
}
pub fn from_csr(csr: &CsrMatrix<T>, partition: HybPartition) -> SparseResult<Self> {
let (h_row_ptr, h_col_idx, h_values) = csr.to_host()?;
let rows = csr.rows() as usize;
let cols = csr.cols() as usize;
let mut row_nnz = Vec::with_capacity(rows);
for i in 0..rows {
row_nnz.push((h_row_ptr[i + 1] - h_row_ptr[i]) as usize);
}
let ell_width = compute_ell_width(&row_nnz, partition);
Self::build_from_csr_host(rows, cols, ell_width, &h_row_ptr, &h_col_idx, &h_values)
}
pub fn from_coo(coo: &super::CooMatrix<T>, partition: HybPartition) -> SparseResult<Self> {
let csr = coo.to_csr()?;
Self::from_csr(&csr, partition)
}
pub fn to_csr(&self) -> SparseResult<CsrMatrix<T>> {
let total_nnz = self.total_nnz();
if total_nnz == 0 {
return Err(SparseError::ZeroNnz);
}
let mut row_counts = vec![0usize; self.rows];
for (i, count) in row_counts.iter_mut().enumerate() {
for k in 0..self.ell_width {
let idx = k * self.rows + i;
if self.ell_col_indices[idx] != HYB_ELL_SENTINEL {
*count += 1;
}
}
}
for &r in &self.coo_row_indices {
let row = r as usize;
if row < self.rows {
row_counts[row] += 1;
}
}
let mut h_row_ptr = vec![0i32; self.rows + 1];
for i in 0..self.rows {
h_row_ptr[i + 1] = h_row_ptr[i] + row_counts[i] as i32;
}
let mut h_col_idx = vec![0i32; total_nnz];
let mut h_values = vec![T::gpu_zero(); total_nnz];
let mut write_pos: Vec<i32> = h_row_ptr.clone();
for (i, pos) in write_pos.iter_mut().enumerate().take(self.rows) {
for k in 0..self.ell_width {
let idx = k * self.rows + i;
let col = self.ell_col_indices[idx];
if col != HYB_ELL_SENTINEL {
let dest = *pos as usize;
h_col_idx[dest] = col;
h_values[dest] = self.ell_values[idx];
*pos += 1;
}
}
}
for j in 0..self.coo_nnz {
let row = self.coo_row_indices[j] as usize;
if row < self.rows {
let dest = write_pos[row] as usize;
h_col_idx[dest] = self.coo_col_indices[j];
h_values[dest] = self.coo_values[j];
write_pos[row] += 1;
}
}
CsrMatrix::from_host(
self.rows as u32,
self.cols as u32,
&h_row_ptr,
&h_col_idx,
&h_values,
)
}
pub fn total_nnz(&self) -> usize {
self.ell_nnz() + self.coo_nnz
}
#[inline]
pub fn nnz(&self) -> usize {
self.total_nnz()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.total_nnz() == 0
}
pub fn ell_nnz(&self) -> usize {
self.ell_col_indices
.iter()
.filter(|&&c| c != HYB_ELL_SENTINEL)
.count()
}
#[inline]
pub fn rows(&self) -> usize {
self.rows
}
#[inline]
pub fn cols(&self) -> usize {
self.cols
}
#[inline]
pub fn ell_width(&self) -> usize {
self.ell_width
}
#[inline]
pub fn ell_col_indices(&self) -> &[i32] {
&self.ell_col_indices
}
#[inline]
pub fn ell_values(&self) -> &[T] {
&self.ell_values
}
#[inline]
pub fn coo_nnz(&self) -> usize {
self.coo_nnz
}
#[inline]
pub fn coo_row_indices(&self) -> &[i32] {
&self.coo_row_indices
}
#[inline]
pub fn coo_col_indices(&self) -> &[i32] {
&self.coo_col_indices
}
#[inline]
pub fn coo_values(&self) -> &[T] {
&self.coo_values
}
pub fn statistics(&self) -> HybStatistics {
let ell_nnz = self.ell_nnz();
let total_nnz = ell_nnz + self.coo_nnz;
let (ell_fraction, coo_fraction) = if total_nnz == 0 {
(0.0, 0.0)
} else {
(
ell_nnz as f64 / total_nnz as f64,
self.coo_nnz as f64 / total_nnz as f64,
)
};
let ell_total_slots = self.rows * self.ell_width;
let ell_padding_ratio = if ell_total_slots == 0 {
0.0
} else {
(ell_total_slots - ell_nnz) as f64 / ell_total_slots as f64
};
let ell_mem = ell_total_slots * (mem::size_of::<i32>() + mem::size_of::<T>());
let coo_mem = self.coo_nnz * (2 * mem::size_of::<i32>() + mem::size_of::<T>());
let memory_bytes = ell_mem + coo_mem;
let csr_memory_bytes = (self.rows + 1) * mem::size_of::<i32>()
+ total_nnz * mem::size_of::<i32>()
+ total_nnz * mem::size_of::<T>();
HybStatistics {
ell_fraction,
coo_fraction,
ell_padding_ratio,
memory_bytes,
csr_memory_bytes,
}
}
fn build_from_csr_host(
rows: usize,
cols: usize,
ell_width: usize,
h_row_ptr: &[i32],
h_col_idx: &[i32],
h_values: &[T],
) -> SparseResult<Self> {
let ell_total = rows * ell_width;
let mut ell_col_indices = vec![HYB_ELL_SENTINEL; ell_total];
let mut ell_values = vec![T::gpu_zero(); ell_total];
let mut coo_row_indices = Vec::new();
let mut coo_col_indices = Vec::new();
let mut coo_values = Vec::new();
for i in 0..rows {
let start = h_row_ptr[i] as usize;
let end = h_row_ptr[i + 1] as usize;
let row_entries = end - start;
let ell_count = row_entries.min(ell_width);
for k in 0..ell_count {
let ell_idx = k * rows + i; ell_col_indices[ell_idx] = h_col_idx[start + k];
ell_values[ell_idx] = h_values[start + k];
}
if row_entries > ell_width {
for j in (start + ell_width)..end {
coo_row_indices.push(i as i32);
coo_col_indices.push(h_col_idx[j]);
coo_values.push(h_values[j]);
}
}
}
let coo_nnz = coo_values.len();
Ok(Self {
rows,
cols,
ell_width,
ell_col_indices,
ell_values,
coo_nnz,
coo_row_indices,
coo_col_indices,
coo_values,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
fn hyb_from_csr_host(
rows: usize,
cols: usize,
row_ptr: &[i32],
col_idx: &[i32],
values: &[f64],
partition: HybPartition,
) -> SparseResult<HybMatrix<f64>> {
let mut row_nnz = Vec::with_capacity(rows);
for i in 0..rows {
row_nnz.push((row_ptr[i + 1] - row_ptr[i]) as usize);
}
let ell_width = compute_ell_width(&row_nnz, partition);
HybMatrix::build_from_csr_host(rows, cols, ell_width, row_ptr, col_idx, values)
}
fn test_csr_data() -> (usize, usize, Vec<i32>, Vec<i32>, Vec<f64>) {
let rows = 4;
let cols = 4;
let row_ptr = vec![0, 1, 2, 4, 7];
let col_idx = vec![0, 1, 0, 2, 0, 1, 3];
let values = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0];
(rows, cols, row_ptr, col_idx, values)
}
#[test]
fn hyb_from_csr_auto_partition() {
let (rows, cols, row_ptr, col_idx, values) = test_csr_data();
let hyb = hyb_from_csr_host(rows, cols, &row_ptr, &col_idx, &values, HybPartition::Auto);
assert!(hyb.is_ok());
let hyb = hyb.expect("test helper");
assert_eq!(hyb.rows(), 4);
assert_eq!(hyb.cols(), 4);
assert_eq!(hyb.ell_width(), 1);
assert_eq!(hyb.ell_nnz(), 4);
assert_eq!(hyb.coo_nnz(), 3);
assert_eq!(hyb.total_nnz(), 7);
}
#[test]
fn hyb_from_csr_max_partition() {
let (rows, cols, row_ptr, col_idx, values) = test_csr_data();
let hyb = hyb_from_csr_host(rows, cols, &row_ptr, &col_idx, &values, HybPartition::Max);
let hyb = hyb.expect("test helper");
assert_eq!(hyb.ell_width(), 3);
assert_eq!(hyb.coo_nnz(), 0);
assert_eq!(hyb.total_nnz(), 7);
}
#[test]
fn hyb_from_csr_fixed_partition() {
let (rows, cols, row_ptr, col_idx, values) = test_csr_data();
let hyb = hyb_from_csr_host(
rows,
cols,
&row_ptr,
&col_idx,
&values,
HybPartition::Fixed(2),
);
let hyb = hyb.expect("test helper");
assert_eq!(hyb.ell_width(), 2);
assert_eq!(hyb.ell_nnz(), 6);
assert_eq!(hyb.coo_nnz(), 1);
assert_eq!(hyb.total_nnz(), 7);
}
#[test]
fn hyb_from_csr_threshold_partition() {
let (rows, cols, row_ptr, col_idx, values) = test_csr_data();
let hyb = hyb_from_csr_host(
rows,
cols,
&row_ptr,
&col_idx,
&values,
HybPartition::Threshold(0.9),
);
let hyb = hyb.expect("test helper");
assert_eq!(hyb.ell_width(), 3);
assert_eq!(hyb.coo_nnz(), 0);
}
#[test]
fn hyb_to_csr_roundtrip() {
let (rows, cols, row_ptr, col_idx, values) = test_csr_data();
let hyb = hyb_from_csr_host(
rows,
cols,
&row_ptr,
&col_idx,
&values,
HybPartition::Fixed(2),
);
let hyb = hyb.expect("test helper");
assert_eq!(hyb.total_nnz(), 7);
assert_eq!(hyb.ell_col_indices()[0], 0);
assert_eq!(hyb.ell_col_indices()[1], 1);
assert_eq!(hyb.ell_col_indices()[2], 0);
assert_eq!(hyb.ell_col_indices()[3], 0);
assert_eq!(hyb.ell_col_indices()[6], 2);
assert_eq!(hyb.ell_col_indices()[7], 1);
assert_eq!(hyb.coo_row_indices(), &[3]);
assert_eq!(hyb.coo_col_indices(), &[3]);
assert!((hyb.coo_values()[0] - 7.0).abs() < 1e-12);
}
#[test]
fn hyb_is_empty_all_padding() {
let ell_col = vec![HYB_ELL_SENTINEL; 4];
let ell_val = vec![0.0f64; 4];
let hyb = HybMatrix::new(2, 2, 2, ell_col, ell_val, vec![], vec![], vec![]);
let hyb = hyb.expect("test helper");
assert!(hyb.is_empty());
assert_eq!(hyb.nnz(), 0);
}
#[test]
fn hyb_statistics_pure_ell() {
let (rows, cols, row_ptr, col_idx, values) = test_csr_data();
let hyb = hyb_from_csr_host(rows, cols, &row_ptr, &col_idx, &values, HybPartition::Max);
let hyb = hyb.expect("test helper");
let stats = hyb.statistics();
assert!((stats.ell_fraction - 1.0).abs() < 1e-12);
assert!((stats.coo_fraction - 0.0).abs() < 1e-12);
assert!((stats.ell_padding_ratio - 5.0 / 12.0).abs() < 1e-12);
assert!(stats.memory_bytes > 0);
assert!(stats.csr_memory_bytes > 0);
}
#[test]
fn hyb_statistics_mixed() {
let (rows, cols, row_ptr, col_idx, values) = test_csr_data();
let hyb = hyb_from_csr_host(
rows,
cols,
&row_ptr,
&col_idx,
&values,
HybPartition::Fixed(1),
);
let hyb = hyb.expect("test helper");
let stats = hyb.statistics();
assert!((stats.ell_fraction - 4.0 / 7.0).abs() < 1e-12);
assert!((stats.coo_fraction - 3.0 / 7.0).abs() < 1e-12);
assert!((stats.ell_padding_ratio - 0.0).abs() < 1e-12);
}
#[test]
fn hyb_new_validation_bad_ell_lengths() {
let result = HybMatrix::<f64>::new(
2,
2,
2,
vec![0; 3], vec![1.0; 4],
vec![],
vec![],
vec![],
);
assert!(result.is_err());
}
#[test]
fn hyb_new_validation_bad_coo_lengths() {
let result = HybMatrix::<f64>::new(
2,
2,
1,
vec![HYB_ELL_SENTINEL; 2],
vec![0.0; 2],
vec![0], vec![0, 1], vec![1.0], );
assert!(result.is_err());
}
#[test]
fn hyb_new_validation_zero_rows() {
let result = HybMatrix::<f64>::new(0, 2, 1, vec![], vec![], vec![], vec![], vec![]);
assert!(result.is_err());
}
#[test]
fn hyb_new_validation_zero_ell_width() {
let result = HybMatrix::<f64>::new(2, 2, 0, vec![], vec![], vec![], vec![], vec![]);
assert!(result.is_err());
}
#[test]
fn hyb_ell_values_column_major_layout() {
let row_ptr = vec![0, 2, 3, 6];
let col_idx = vec![0, 1, 2, 0, 1, 2];
let values = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
let hyb = hyb_from_csr_host(3, 3, &row_ptr, &col_idx, &values, HybPartition::Fixed(2));
let hyb = hyb.expect("test helper");
assert_eq!(hyb.ell_col_indices()[0], 0);
assert!((hyb.ell_values()[0] - 1.0).abs() < 1e-12);
assert_eq!(hyb.ell_col_indices()[1], 2);
assert!((hyb.ell_values()[1] - 3.0).abs() < 1e-12);
assert_eq!(hyb.ell_col_indices()[2], 0);
assert_eq!(hyb.ell_col_indices()[3], 1);
assert!((hyb.ell_values()[3] - 2.0).abs() < 1e-12);
assert_eq!(hyb.ell_col_indices()[4], HYB_ELL_SENTINEL);
assert_eq!(hyb.ell_col_indices()[5], 1);
assert_eq!(hyb.coo_nnz(), 1);
assert_eq!(hyb.coo_row_indices(), &[2]);
assert_eq!(hyb.coo_col_indices(), &[2]);
assert!((hyb.coo_values()[0] - 6.0).abs() < 1e-12);
}
#[test]
fn optimal_ell_width_basic() {
let row_nnz = vec![2, 2, 2, 2];
let w = optimal_ell_width::<f64>(&row_nnz);
assert_eq!(w, 2);
}
#[test]
fn optimal_ell_width_skewed() {
let mut row_nnz = vec![1; 99];
row_nnz.push(100);
let w = optimal_ell_width::<f64>(&row_nnz);
assert!(w < 10, "expected small ELL width, got {w}");
}
#[test]
fn optimal_ell_width_empty() {
let w = optimal_ell_width::<f64>(&[]);
assert_eq!(w, 1);
}
#[test]
fn optimal_ell_width_all_zero() {
let w = optimal_ell_width::<f64>(&[0, 0, 0]);
assert_eq!(w, 1);
}
#[test]
fn hyb_partition_threshold_boundary() {
let (rows, cols, row_ptr, col_idx, values) = test_csr_data();
let hyb = hyb_from_csr_host(
rows,
cols,
&row_ptr,
&col_idx,
&values,
HybPartition::Threshold(1.0),
);
let hyb = hyb.expect("test helper");
assert_eq!(hyb.ell_width(), 3);
assert_eq!(hyb.coo_nnz(), 0);
}
#[test]
fn hyb_partition_threshold_zero() {
let (rows, cols, row_ptr, col_idx, values) = test_csr_data();
let hyb = hyb_from_csr_host(
rows,
cols,
&row_ptr,
&col_idx,
&values,
HybPartition::Threshold(0.0),
);
let hyb = hyb.expect("test helper");
assert_eq!(hyb.ell_width(), 1);
}
#[test]
fn hyb_statistics_memory_comparison() {
let (rows, cols, row_ptr, col_idx, values) = test_csr_data();
let hyb = hyb_from_csr_host(
rows,
cols,
&row_ptr,
&col_idx,
&values,
HybPartition::Fixed(2),
);
let hyb = hyb.expect("test helper");
let stats = hyb.statistics();
let expected_ell = 4 * 2 * (std::mem::size_of::<i32>() + std::mem::size_of::<f64>());
let expected_coo = 2 * std::mem::size_of::<i32>() + std::mem::size_of::<f64>();
assert_eq!(stats.memory_bytes, expected_ell + expected_coo);
let expected_csr = 5 * std::mem::size_of::<i32>()
+ 7 * std::mem::size_of::<i32>()
+ 7 * std::mem::size_of::<f64>();
assert_eq!(stats.csr_memory_bytes, expected_csr);
}
#[test]
fn hyb_identity_4x4_no_coo_overflow() {
let rows = 4usize;
let cols = 4usize;
let row_ptr = vec![0i32, 1, 2, 3, 4];
let col_idx = vec![0i32, 1, 2, 3];
let values = vec![1.0f64; 4];
let hyb = hyb_from_csr_host(rows, cols, &row_ptr, &col_idx, &values, HybPartition::Auto)
.expect("identity 4x4 hyb construction");
assert_eq!(hyb.ell_width(), 1, "ell_width should be 1 for identity");
assert_eq!(
hyb.coo_nnz(),
0,
"no COO overflow for uniform-density matrix"
);
assert_eq!(hyb.total_nnz(), 4);
}
#[test]
fn hyb_irregular_matrix_has_coo_entries() {
let rows = 4usize;
let cols = 5usize;
let row_ptr = vec![0i32, 5, 6, 7, 8];
let col_idx = vec![0i32, 1, 2, 3, 4, 0, 1, 2];
let values = vec![1.0f64, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0];
let hyb = hyb_from_csr_host(rows, cols, &row_ptr, &col_idx, &values, HybPartition::Auto)
.expect("irregular hyb construction");
assert_eq!(hyb.ell_width(), 1);
assert_eq!(
hyb.coo_nnz(),
4,
"row 0 overflows 4 entries into COO (5 nnz with ell_width=1)"
);
}
#[test]
fn hyb_spmv_matches_csr() {
let rows = 4usize;
let cols = 4usize;
let row_ptr = vec![0i32, 2, 4, 6, 8];
let col_idx = vec![0i32, 1, 1, 2, 2, 3, 3, 0];
let values = vec![2.0f64, 1.0, 2.0, 1.0, 2.0, 1.0, 2.0, 1.0];
let x = [1.0f64, 2.0, 3.0, 4.0];
let mut y_csr = vec![0.0f64; rows];
for i in 0..rows {
let start = row_ptr[i] as usize;
let end = row_ptr[i + 1] as usize;
for j in start..end {
y_csr[i] += values[j] * x[col_idx[j] as usize];
}
}
let hyb = hyb_from_csr_host(
rows,
cols,
&row_ptr,
&col_idx,
&values,
HybPartition::Fixed(1),
)
.expect("banded hyb construction");
assert_eq!(hyb.coo_nnz(), 4, "each row overflows 1 entry to COO");
let mut y_hyb = vec![0.0f64; rows];
for k in 0..hyb.ell_width() {
for (i, y_val) in y_hyb.iter_mut().enumerate() {
let idx = k * rows + i;
let c = hyb.ell_col_indices()[idx];
if c >= 0 {
*y_val += hyb.ell_values()[idx] * x[c as usize];
}
}
}
for idx in 0..hyb.coo_nnz() {
let r = hyb.coo_row_indices()[idx] as usize;
let c = hyb.coo_col_indices()[idx] as usize;
y_hyb[r] += hyb.coo_values()[idx] * x[c];
}
for i in 0..rows {
assert!(
(y_hyb[i] - y_csr[i]).abs() < 1e-10,
"HYB SpMV mismatch at row {}: hyb={}, csr={}",
i,
y_hyb[i],
y_csr[i]
);
}
}
#[test]
fn hyb_ell_width_is_avg_nnz() {
let rows = 4usize;
let cols = 4usize;
let row_ptr = vec![0i32, 2, 4, 6, 8];
let col_idx = vec![0i32, 1, 1, 2, 2, 3, 3, 0];
let values = vec![1.0f64; 8];
let hyb = hyb_from_csr_host(
rows,
cols,
&row_ptr,
&col_idx,
&values,
HybPartition::Fixed(2),
)
.expect("uniform 2-per-row hyb construction");
assert_eq!(
hyb.ell_width(),
2,
"ell_width should be 2 (= avg nnz per row)"
);
assert_eq!(
hyb.coo_nnz(),
0,
"no overflow when ell_width == max nnz per row"
);
}
#[test]
fn hyb_coo_stores_overflow() {
let rows = 4usize;
let cols = 4usize;
let row_ptr = vec![0i32, 3, 4, 5, 6];
let col_idx = vec![0i32, 1, 2, 1, 2, 3];
let values = vec![10.0f64, 20.0, 30.0, 40.0, 50.0, 60.0];
let hyb = hyb_from_csr_host(
rows,
cols,
&row_ptr,
&col_idx,
&values,
HybPartition::Fixed(1),
)
.expect("overflow test hyb construction");
assert_eq!(hyb.coo_nnz(), 2, "row 0 overflows 2 entries to COO");
for &r in hyb.coo_row_indices() {
assert_eq!(r, 0i32, "all COO overflow entries should be from row 0");
}
}
}