Skip to main content

jay/
sparse.rs

1//! Sparse arrays: the storage kind behind J's `$.`.
2//!
3//! A sparse array has the shape of the array it stands for and holds only
4//! the positions that differ from one repeated element — the SPARSE
5//! ELEMENT, which is zero for anything [`sparsify`] makes. Some of the axes
6//! are stored sparsely and the rest are dense, so one stored entry is a
7//! whole cell over the dense axes: an index row naming the entry's position
8//! along the sparse axes, and that cell's elements. When every axis is
9//! sparse — what `$. y` always produces — the cell is a single element and
10//! the array is the familiar list of coordinates and values.
11//!
12//! The shape and the stored values live on the [`Array`] itself: `shape` is
13//! the LOGICAL shape and `data` is the stored cells end to end, so an array
14//! of sparse doubles reports the same dtype and formats its values through
15//! the same code a dense one does. Everything else is in [`Sparse`], which
16//! the array carries behind an `Arc`.
17//!
18//! Only `$.` itself, the display and `":` read the stored form. Every other
19//! verb receives [`Array::densified`], which is semantically exact and says
20//! nothing about how fast it is; the caveat is written down in
21//! docs/status.md.
22
23use std::sync::Arc;
24
25use crate::array::{Array, Data};
26use crate::complex::Cx;
27use crate::dtype::DType;
28use crate::error::{Error, ErrorKind, Result, Span};
29use crate::exact::{Ext, Rat};
30
31/// What a sparse array holds besides its shape and its stored cells.
32#[derive(Clone, Debug, PartialEq)]
33pub struct Sparse {
34    /// The axes stored sparsely, ascending and distinct. Every other axis
35    /// of the shape is dense and forms the stored cell.
36    pub axes: Vec<usize>,
37    /// One row per stored entry, `axes.len()` columns, row-major: entry
38    /// `e`'s position along sparse axis `axes[j]` is `indices[e * k + j]`.
39    pub indices: Vec<usize>,
40    /// The element every position not named by `indices` holds. Exactly one
41    /// element, of the array's own dtype.
42    pub fill: Data,
43    /// Stored entries. It is not derivable from the buffers when there are
44    /// no sparse axes at all, or when a dense axis has length zero.
45    pub entries: usize,
46}
47
48impl Sparse {
49    /// The shape of one stored cell: the lengths of the axes that are not
50    /// sparse, in axis order.
51    pub fn cell_shape(&self, shape: &[usize]) -> Vec<usize> {
52        shape
53            .iter()
54            .enumerate()
55            .filter(|(k, _)| !self.axes.contains(k))
56            .map(|(_, &n)| n)
57            .collect()
58    }
59
60    /// Elements in one stored cell.
61    pub fn cell_size(&self, shape: &[usize]) -> usize {
62        self.cell_shape(shape).iter().product()
63    }
64}
65
66/// Where each stored entry and each element of its cell lands in the dense
67/// buffer. Worked out once per expansion, then read by whichever element
68/// type the array holds.
69struct Plan {
70    /// The offset of entry `e`'s cell origin.
71    bases: Vec<usize>,
72    /// The offset of each element within a cell, relative to that origin.
73    cell: Vec<usize>,
74}
75
76fn plan(shape: &[usize], s: &Sparse) -> Plan {
77    let rank = shape.len();
78    let mut strides = vec![1usize; rank];
79    for k in (0..rank.saturating_sub(1)).rev() {
80        strides[k] = strides[k + 1] * shape[k + 1];
81    }
82    let k = s.axes.len();
83    let bases = (0..s.entries)
84        .map(|e| {
85            (0..k).map(|j| s.indices[e * k + j] * strides[s.axes[j]]).sum::<usize>()
86        })
87        .collect();
88    // The dense axes, walked as an odometer, give the offsets inside a cell
89    // in the order the cell's own elements are stored.
90    let dense: Vec<usize> = (0..rank).filter(|k| !s.axes.contains(k)).collect();
91    let mut cell = Vec::with_capacity(s.cell_size(shape));
92    let mut coord = vec![0usize; dense.len()];
93    let cells = s.cell_size(shape);
94    for _ in 0..cells {
95        cell.push(coord.iter().zip(&dense).map(|(&c, &ax)| c * strides[ax]).sum());
96        let mut j = dense.len();
97        while j > 0 {
98            j -= 1;
99            coord[j] += 1;
100            if coord[j] < shape[dense[j]] {
101                break;
102            }
103            coord[j] = 0;
104        }
105    }
106    Plan { bases, cell }
107}
108
109/// The dense buffer of an array whose stored cells are `values`.
110fn expand<T: Clone>(values: &[T], fill: &T, count: usize, p: &Plan) -> Vec<T> {
111    let mut out = vec![fill.clone(); count];
112    let width = p.cell.len();
113    for (e, &base) in p.bases.iter().enumerate() {
114        for (c, &off) in p.cell.iter().enumerate() {
115            out[base + off] = values[e * width + c].clone();
116        }
117    }
118    out
119}
120
121/// The array `a`, which must be sparse, with every position materialised.
122pub(crate) fn densify(a: &Array, s: &Sparse) -> Array {
123    let count: usize = a.shape.iter().product();
124    let p = plan(&a.shape, s);
125    macro_rules! by {
126        ($($variant:ident),*) => {
127            match (&a.data, &s.fill) {
128                $((Data::$variant(v), Data::$variant(f)) => {
129                    Data::$variant(expand(v, &f[0], count, &p).into())
130                })*
131                // A sparse array is built in one place and always carries a
132                // fill of its own dtype.
133                _ => Data::empty(a.dtype()),
134            }
135        };
136    }
137    let data = by!(Bool, I64, Ext, Rat, F64, Complex, Char, Symbol, Box);
138    Array::new(a.shape.clone(), data)
139}
140
141/// The positions of `a` that differ from zero, in ravel order.
142fn nonzero(a: &Array) -> Vec<usize> {
143    fn of<T: PartialEq>(v: &[T], zero: T) -> Vec<usize> {
144        v.iter().enumerate().filter(|(_, x)| **x != zero).map(|(i, _)| i).collect()
145    }
146    match &a.data {
147        Data::Bool(v) => of(v, 0),
148        Data::I64(v) => of(v, 0),
149        Data::F64(v) => of(v, 0.0),
150        Data::Complex(v) => of(v, crate::complex::ZERO),
151        _ => Vec::new(),
152    }
153}
154
155/// One zero of this dtype, as the sparse element of a converted array.
156fn zero_of(dtype: DType) -> Data {
157    match dtype {
158        DType::Bool => Data::Bool(vec![0u8].into()),
159        DType::I64 => Data::I64(vec![0i64].into()),
160        DType::F64 => Data::F64(vec![0.0f64].into()),
161        DType::Complex => Data::Complex(vec![crate::complex::ZERO].into()),
162        DType::Ext => Data::Ext(vec![Ext::default()].into()),
163        DType::Rat => Data::Rat(vec![Rat::zero()].into()),
164        DType::Char => Data::Char(vec![' '].into()),
165        DType::Symbol => Data::Symbol(vec![crate::symbol::EMPTY].into()),
166        DType::Box => Data::Box(vec![Array::box_fill()].into()),
167    }
168}
169
170/// The element types that can be stored sparsely. J has a code for sparse
171/// characters and sparse boxes and refuses to make either; the exact types
172/// have no sparse form at all.
173fn check_storable(a: &Array, span: Span) -> Result<()> {
174    match a.dtype() {
175        DType::Bool | DType::I64 | DType::F64 | DType::Complex => Ok(()),
176        DType::Char | DType::Box | DType::Symbol => Err(Error::not_yet(
177            format!("a sparse array of {}", a.dtype().name()),
178            span,
179        )),
180        DType::Ext | DType::Rat => Err(Error::domain(
181            format!("{} has no sparse form", a.dtype().name()),
182            span,
183        )),
184    }
185}
186
187/// `$. y`: the dense array `y` in sparse form, every axis sparse and zero
188/// the sparse element. A sparse argument comes back unchanged, and a scalar
189/// stays dense — there is no axis to store it along.
190pub fn sparsify(y: &Array, span: Span) -> Result<Array> {
191    if y.is_sparse() {
192        return Ok(y.clone());
193    }
194    let y = y.to_row_major();
195    if y.rank() == 0 {
196        return Ok(y);
197    }
198    check_storable(&y, span)?;
199    let rank = y.rank();
200    let mut strides = vec![1usize; rank];
201    for k in (0..rank - 1).rev() {
202        strides[k] = strides[k + 1] * y.shape[k + 1];
203    }
204    let at = nonzero(&y);
205    let mut indices = Vec::with_capacity(at.len() * rank);
206    let mut values = Data::empty(y.dtype());
207    for &i in &at {
208        let mut rest = i;
209        for &stride in &strides {
210            indices.push(rest / stride);
211            rest %= stride;
212        }
213        values.push_from(&y.data, i);
214    }
215    let s = Sparse {
216        axes: (0..rank).collect(),
217        indices,
218        fill: zero_of(y.dtype()),
219        entries: at.len(),
220    };
221    Ok(Array::sparse(y.shape.clone(), values, s))
222}
223
224/// `1 $. y`: a new sparse array with nothing stored in it. `y` is the
225/// shape, or a boxed `shape ; axes`, or a boxed `shape ; axes ; element`.
226/// Left to itself the whole shape is sparse and the element is a float
227/// zero, which is what J's own bare form gives.
228pub fn create(y: &Array, span: Span) -> Result<Array> {
229    let parts: Vec<Array> = match y.as_boxes() {
230        Some(b) if y.rank() <= 1 => b.iter().map(|a| a.densified()).collect(),
231        _ => vec![y.densified()],
232    };
233    if parts.is_empty() || parts.len() > 3 {
234        return Err(Error::new(
235            ErrorKind::Length,
236            "a sparse array is made from a shape, or a shape and its sparse axes, or those and the element that fills it",
237            Some(span),
238        ));
239    }
240    let shape = axis_lengths(&parts[0], span)?;
241    let rank = shape.len();
242    let axes = match parts.get(1) {
243        None => (0..rank).collect(),
244        Some(a) => sparse_axes(a, rank, span)?,
245    };
246    let fill = match parts.get(2) {
247        // J's own default is a floating-point zero, so a bare `1 $. shape`
248        // is a sparse array of doubles.
249        None => Data::F64(vec![0.0].into()),
250        Some(a) => {
251            if a.rank() != 0 {
252                return Err(Error::new(
253                    ErrorKind::Rank,
254                    "the element that fills a sparse array is one atom",
255                    Some(span),
256                ));
257            }
258            a.data.slice(0, 1)
259        }
260    };
261    let empty = Array::new(vec![0], fill.slice(0, 0));
262    check_storable(&empty, span)?;
263    // The dense expansion is what every other verb will ask for, so a shape
264    // too large to hold is refused here rather than at the first use.
265    crate::limits::elements(&shape, span)?;
266    let s = Sparse { axes, indices: Vec::new(), fill, entries: 0 };
267    Ok(Array::sparse(shape, Data::empty(empty.dtype()), s))
268}
269
270/// A shape argument: an atom or a list of non-negative axis lengths.
271fn axis_lengths(a: &Array, span: Span) -> Result<Vec<usize>> {
272    if a.rank() > 1 {
273        return Err(Error::new(ErrorKind::Rank, "a shape is a list, not a table", Some(span)));
274    }
275    let Some(v) = a.to_i64_vec() else {
276        return Err(Error::domain("a shape is made of integers", span));
277    };
278    if v.is_empty() {
279        return Err(Error::new(
280            ErrorKind::Length,
281            "a sparse array needs at least one axis",
282            Some(span),
283        ));
284    }
285    let mut shape = Vec::with_capacity(v.len());
286    for n in v {
287        if n < 0 {
288            return Err(Error::domain("an axis length cannot be negative", span));
289        }
290        shape.push(n as usize);
291    }
292    Ok(shape)
293}
294
295/// The sparse-axis list: distinct axes of the shape, put in ascending order.
296fn sparse_axes(a: &Array, rank: usize, span: Span) -> Result<Vec<usize>> {
297    if a.rank() > 1 {
298        return Err(Error::new(
299            ErrorKind::Rank,
300            "the sparse axes are a list, not a table",
301            Some(span),
302        ));
303    }
304    let Some(v) = a.to_i64_vec() else {
305        return Err(Error::domain("the sparse axes are integers", span));
306    };
307    let mut axes: Vec<usize> = Vec::with_capacity(v.len());
308    for k in v {
309        if k < 0 || k as usize >= rank || axes.contains(&(k as usize)) {
310            return Err(Error::new(
311                ErrorKind::Domain,
312                format!("{k} is not an axis of a rank-{rank} array, or names one twice"),
313                Some(span),
314            ));
315        }
316        axes.push(k as usize);
317    }
318    axes.sort_unstable();
319    Ok(axes)
320}
321
322/// `8 $. y`: the same array with every stored entry whose cell is entirely
323/// the sparse element dropped. Amending a stored position back to the fill
324/// leaves the entry behind; this is what removes it.
325pub fn compress(a: &Array, s: &Sparse) -> Array {
326    let width = s.cell_size(&a.shape);
327    let k = s.axes.len();
328    let keep: Vec<usize> = (0..s.entries)
329        .filter(|&e| (0..width).any(|c| !same_as_fill(&a.data, e * width + c, &s.fill)))
330        .collect();
331    let mut indices = Vec::with_capacity(keep.len() * k);
332    let mut values = Data::empty(a.dtype());
333    for &e in &keep {
334        indices.extend_from_slice(&s.indices[e * k..(e + 1) * k]);
335        for c in 0..width {
336            values.push_from(&a.data, e * width + c);
337        }
338    }
339    let out = Sparse { axes: s.axes.clone(), indices, fill: s.fill.clone(), entries: keep.len() };
340    Array::sparse(a.shape.clone(), values, out)
341}
342
343/// Whether element `i` of `data` is the sparse element `fill` holds.
344fn same_as_fill(data: &Data, i: usize, fill: &Data) -> bool {
345    fn at<T: Clone + PartialEq>(v: &[T], i: usize, f: &[T]) -> bool {
346        v[i] == f[0]
347    }
348    match (data, fill) {
349        (Data::Bool(v), Data::Bool(f)) => at(v, i, f),
350        (Data::I64(v), Data::I64(f)) => at(v, i, f),
351        (Data::F64(v), Data::F64(f)) => at(v, i, f),
352        (Data::Complex(v), Data::Complex(f)) => at::<Cx>(v, i, f),
353        _ => false,
354    }
355}
356
357/// The stored cells as an ordinary array: one leading axis of entries, then
358/// the cell's own shape. This is `5 $. y`.
359pub fn values_of(a: &Array, s: &Sparse) -> Array {
360    let mut shape = vec![s.entries];
361    shape.extend(s.cell_shape(&a.shape));
362    Array::new(shape, a.data.clone())
363}
364
365/// The stored index rows as an integer table: one row per entry, one column
366/// per sparse axis. This is `4 $. y`.
367pub fn indices_of(s: &Sparse) -> Array {
368    let values: Vec<i64> = s.indices.iter().map(|&i| i as i64).collect();
369    Array::new(vec![s.entries, s.axes.len()], Data::I64(values.into()))
370}
371
372/// The shape, the sparse axes and the sparse element, each boxed. This is
373/// `_1 $. y`.
374pub fn attributes(a: &Array, s: &Sparse) -> Array {
375    let shape = Array::from_i64(a.shape.iter().map(|&n| n as i64).collect());
376    let axes = Array::from_i64(s.axes.iter().map(|&k| k as i64).collect());
377    let fill = Array::new(vec![], s.fill.clone());
378    Array::new(vec![3], Data::Box(vec![shape, axes, fill].into()))
379}
380
381/// The sparse element on its own, as an atom. This is `3 $. y`.
382pub fn fill_of(s: &Sparse) -> Array {
383    Array::new(vec![], s.fill.clone())
384}
385
386/// A sparse array carried behind an `Arc`, which is what the array itself
387/// holds so that cloning a sparse value stays a refcount bump.
388pub(crate) type Handle = Arc<Sparse>;