use std::sync::Arc;
use crate::array::{Array, Data};
use crate::complex::Cx;
use crate::dtype::DType;
use crate::error::{Error, ErrorKind, Result, Span};
use crate::exact::{Ext, Rat};
#[derive(Clone, Debug, PartialEq)]
pub struct Sparse {
pub axes: Vec<usize>,
pub indices: Vec<usize>,
pub fill: Data,
pub entries: usize,
}
impl Sparse {
pub fn cell_shape(&self, shape: &[usize]) -> Vec<usize> {
shape
.iter()
.enumerate()
.filter(|(k, _)| !self.axes.contains(k))
.map(|(_, &n)| n)
.collect()
}
pub fn cell_size(&self, shape: &[usize]) -> usize {
self.cell_shape(shape).iter().product()
}
}
struct Plan {
bases: Vec<usize>,
cell: Vec<usize>,
}
fn plan(shape: &[usize], s: &Sparse) -> Plan {
let rank = shape.len();
let mut strides = vec![1usize; rank];
for k in (0..rank.saturating_sub(1)).rev() {
strides[k] = strides[k + 1] * shape[k + 1];
}
let k = s.axes.len();
let bases = (0..s.entries)
.map(|e| {
(0..k).map(|j| s.indices[e * k + j] * strides[s.axes[j]]).sum::<usize>()
})
.collect();
let dense: Vec<usize> = (0..rank).filter(|k| !s.axes.contains(k)).collect();
let mut cell = Vec::with_capacity(s.cell_size(shape));
let mut coord = vec![0usize; dense.len()];
let cells = s.cell_size(shape);
for _ in 0..cells {
cell.push(coord.iter().zip(&dense).map(|(&c, &ax)| c * strides[ax]).sum());
let mut j = dense.len();
while j > 0 {
j -= 1;
coord[j] += 1;
if coord[j] < shape[dense[j]] {
break;
}
coord[j] = 0;
}
}
Plan { bases, cell }
}
fn expand<T: Clone>(values: &[T], fill: &T, count: usize, p: &Plan) -> Vec<T> {
let mut out = vec![fill.clone(); count];
let width = p.cell.len();
for (e, &base) in p.bases.iter().enumerate() {
for (c, &off) in p.cell.iter().enumerate() {
out[base + off] = values[e * width + c].clone();
}
}
out
}
pub(crate) fn densify(a: &Array, s: &Sparse) -> Array {
let count: usize = a.shape.iter().product();
let p = plan(&a.shape, s);
macro_rules! by {
($($variant:ident),*) => {
match (&a.data, &s.fill) {
$((Data::$variant(v), Data::$variant(f)) => {
Data::$variant(expand(v, &f[0], count, &p).into())
})*
_ => Data::empty(a.dtype()),
}
};
}
let data = by!(Bool, I64, Ext, Rat, F64, Complex, Char, Symbol, Box);
Array::new(a.shape.clone(), data)
}
fn nonzero(a: &Array) -> Vec<usize> {
fn of<T: PartialEq>(v: &[T], zero: T) -> Vec<usize> {
v.iter().enumerate().filter(|(_, x)| **x != zero).map(|(i, _)| i).collect()
}
match &a.data {
Data::Bool(v) => of(v, 0),
Data::I64(v) => of(v, 0),
Data::F64(v) => of(v, 0.0),
Data::Complex(v) => of(v, crate::complex::ZERO),
_ => Vec::new(),
}
}
fn zero_of(dtype: DType) -> Data {
match dtype {
DType::Bool => Data::Bool(vec![0u8].into()),
DType::I64 => Data::I64(vec![0i64].into()),
DType::F64 => Data::F64(vec![0.0f64].into()),
DType::Complex => Data::Complex(vec![crate::complex::ZERO].into()),
DType::Ext => Data::Ext(vec![Ext::default()].into()),
DType::Rat => Data::Rat(vec![Rat::zero()].into()),
DType::Char => Data::Char(vec![' '].into()),
DType::Symbol => Data::Symbol(vec![crate::symbol::EMPTY].into()),
DType::Box => Data::Box(vec![Array::box_fill()].into()),
}
}
fn check_storable(a: &Array, span: Span) -> Result<()> {
match a.dtype() {
DType::Bool | DType::I64 | DType::F64 | DType::Complex => Ok(()),
DType::Char | DType::Box | DType::Symbol => Err(Error::not_yet(
format!("a sparse array of {}", a.dtype().name()),
span,
)),
DType::Ext | DType::Rat => Err(Error::domain(
format!("{} has no sparse form", a.dtype().name()),
span,
)),
}
}
pub fn sparsify(y: &Array, span: Span) -> Result<Array> {
if y.is_sparse() {
return Ok(y.clone());
}
let y = y.to_row_major();
if y.rank() == 0 {
return Ok(y);
}
check_storable(&y, span)?;
let rank = y.rank();
let mut strides = vec![1usize; rank];
for k in (0..rank - 1).rev() {
strides[k] = strides[k + 1] * y.shape[k + 1];
}
let at = nonzero(&y);
let mut indices = Vec::with_capacity(at.len() * rank);
let mut values = Data::empty(y.dtype());
for &i in &at {
let mut rest = i;
for &stride in &strides {
indices.push(rest / stride);
rest %= stride;
}
values.push_from(&y.data, i);
}
let s = Sparse {
axes: (0..rank).collect(),
indices,
fill: zero_of(y.dtype()),
entries: at.len(),
};
Ok(Array::sparse(y.shape.clone(), values, s))
}
pub fn create(y: &Array, span: Span) -> Result<Array> {
let parts: Vec<Array> = match y.as_boxes() {
Some(b) if y.rank() <= 1 => b.iter().map(|a| a.densified()).collect(),
_ => vec![y.densified()],
};
if parts.is_empty() || parts.len() > 3 {
return Err(Error::new(
ErrorKind::Length,
"a sparse array is made from a shape, or a shape and its sparse axes, or those and the element that fills it",
Some(span),
));
}
let shape = axis_lengths(&parts[0], span)?;
let rank = shape.len();
let axes = match parts.get(1) {
None => (0..rank).collect(),
Some(a) => sparse_axes(a, rank, span)?,
};
let fill = match parts.get(2) {
None => Data::F64(vec![0.0].into()),
Some(a) => {
if a.rank() != 0 {
return Err(Error::new(
ErrorKind::Rank,
"the element that fills a sparse array is one atom",
Some(span),
));
}
a.data.slice(0, 1)
}
};
let empty = Array::new(vec![0], fill.slice(0, 0));
check_storable(&empty, span)?;
crate::limits::elements(&shape, span)?;
let s = Sparse { axes, indices: Vec::new(), fill, entries: 0 };
Ok(Array::sparse(shape, Data::empty(empty.dtype()), s))
}
fn axis_lengths(a: &Array, span: Span) -> Result<Vec<usize>> {
if a.rank() > 1 {
return Err(Error::new(ErrorKind::Rank, "a shape is a list, not a table", Some(span)));
}
let Some(v) = a.to_i64_vec() else {
return Err(Error::domain("a shape is made of integers", span));
};
if v.is_empty() {
return Err(Error::new(
ErrorKind::Length,
"a sparse array needs at least one axis",
Some(span),
));
}
let mut shape = Vec::with_capacity(v.len());
for n in v {
if n < 0 {
return Err(Error::domain("an axis length cannot be negative", span));
}
shape.push(n as usize);
}
Ok(shape)
}
fn sparse_axes(a: &Array, rank: usize, span: Span) -> Result<Vec<usize>> {
if a.rank() > 1 {
return Err(Error::new(
ErrorKind::Rank,
"the sparse axes are a list, not a table",
Some(span),
));
}
let Some(v) = a.to_i64_vec() else {
return Err(Error::domain("the sparse axes are integers", span));
};
let mut axes: Vec<usize> = Vec::with_capacity(v.len());
for k in v {
if k < 0 || k as usize >= rank || axes.contains(&(k as usize)) {
return Err(Error::new(
ErrorKind::Domain,
format!("{k} is not an axis of a rank-{rank} array, or names one twice"),
Some(span),
));
}
axes.push(k as usize);
}
axes.sort_unstable();
Ok(axes)
}
pub fn compress(a: &Array, s: &Sparse) -> Array {
let width = s.cell_size(&a.shape);
let k = s.axes.len();
let keep: Vec<usize> = (0..s.entries)
.filter(|&e| (0..width).any(|c| !same_as_fill(&a.data, e * width + c, &s.fill)))
.collect();
let mut indices = Vec::with_capacity(keep.len() * k);
let mut values = Data::empty(a.dtype());
for &e in &keep {
indices.extend_from_slice(&s.indices[e * k..(e + 1) * k]);
for c in 0..width {
values.push_from(&a.data, e * width + c);
}
}
let out = Sparse { axes: s.axes.clone(), indices, fill: s.fill.clone(), entries: keep.len() };
Array::sparse(a.shape.clone(), values, out)
}
fn same_as_fill(data: &Data, i: usize, fill: &Data) -> bool {
fn at<T: Clone + PartialEq>(v: &[T], i: usize, f: &[T]) -> bool {
v[i] == f[0]
}
match (data, fill) {
(Data::Bool(v), Data::Bool(f)) => at(v, i, f),
(Data::I64(v), Data::I64(f)) => at(v, i, f),
(Data::F64(v), Data::F64(f)) => at(v, i, f),
(Data::Complex(v), Data::Complex(f)) => at::<Cx>(v, i, f),
_ => false,
}
}
pub fn values_of(a: &Array, s: &Sparse) -> Array {
let mut shape = vec![s.entries];
shape.extend(s.cell_shape(&a.shape));
Array::new(shape, a.data.clone())
}
pub fn indices_of(s: &Sparse) -> Array {
let values: Vec<i64> = s.indices.iter().map(|&i| i as i64).collect();
Array::new(vec![s.entries, s.axes.len()], Data::I64(values.into()))
}
pub fn attributes(a: &Array, s: &Sparse) -> Array {
let shape = Array::from_i64(a.shape.iter().map(|&n| n as i64).collect());
let axes = Array::from_i64(s.axes.iter().map(|&k| k as i64).collect());
let fill = Array::new(vec![], s.fill.clone());
Array::new(vec![3], Data::Box(vec![shape, axes, fill].into()))
}
pub fn fill_of(s: &Sparse) -> Array {
Array::new(vec![], s.fill.clone())
}
pub(crate) type Handle = Arc<Sparse>;