use ternlang_core::trit::Trit;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SparseIndex {
pub rows: usize,
pub cols: usize,
pub values: Vec<i8>,
pub col_idx: Vec<u32>,
pub row_ptr: Vec<u64>,
pub nnz: usize,
pub sparsity: f64,
}
impl SparseIndex {
pub fn from_trits(rows: usize, cols: usize, data: &[Trit]) -> Self {
assert_eq!(data.len(), rows * cols);
let mut values = Vec::new();
let mut col_idx = Vec::new();
let mut row_ptr = vec![0u64; rows + 1];
for r in 0..rows {
row_ptr[r] = values.len() as u64;
for c in 0..cols {
let t = data[r * cols + c];
if t != Trit::Tend {
values.push(match t {
Trit::Affirm => 1i8,
Trit::Reject => -1i8,
Trit::Tend => 0i8,
});
col_idx.push(c as u32);
}
}
}
row_ptr[rows] = values.len() as u64;
let nnz = values.len();
let total = rows * cols;
let sparsity = 1.0 - nnz as f64 / total as f64;
Self { rows, cols, values, col_idx, row_ptr, nnz, sparsity }
}
pub fn to_dense(&self) -> Vec<Trit> {
let mut out = vec![Trit::Tend; self.rows * self.cols];
for r in 0..self.rows {
let start = self.row_ptr[r] as usize;
let end = self.row_ptr[r + 1] as usize;
for idx in start..end {
let c = self.col_idx[idx] as usize;
out[r * self.cols + c] = if self.values[idx] > 0 {
Trit::Affirm
} else {
Trit::Reject
};
}
}
out
}
pub fn memory_bytes(&self) -> usize {
self.values.len() + self.col_idx.len() * 4 + self.row_ptr.len() * 8 }
pub fn is_efficient(&self) -> bool {
let dense_packed = (self.rows * self.cols + 3) / 4; self.memory_bytes() < dense_packed
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn roundtrip_sparse() {
let trits = vec![
Trit::Affirm, Trit::Tend, Trit::Reject,
Trit::Tend, Trit::Tend, Trit::Affirm,
Trit::Reject, Trit::Affirm, Trit::Tend,
];
let idx = SparseIndex::from_trits(3, 3, &trits);
assert_eq!(idx.nnz, 5);
let dense = idx.to_dense();
assert_eq!(dense, trits);
}
}