sprs_rssn/sparse/
indptr.rs

1//! This module defines the behavior of types suitable to be used
2//! as `indptr` storage in a [`CsMatBase`].
3//!
4//! [`CsMatBase`]: type.CsMatBase.html
5
6#[cfg(feature = "serde")]
7use super::serde_traits::IndPtrBaseShadow;
8use crate::errors::StructureError;
9use crate::indexing::SpIndex;
10#[cfg(feature = "serde")]
11use serde::{Deserialize, Serialize};
12use std::ops::Range;
13use std::ops::{Deref, DerefMut};
14
15#[derive(Eq, PartialEq, Debug, Copy, Clone, Hash)]
16#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
17#[cfg_attr(
18    feature = "serde",
19    serde(try_from = "IndPtrBaseShadow<Iptr, Storage>")
20)]
21pub struct IndPtrBase<Iptr, Storage>
22where
23    Iptr: SpIndex,
24    Storage: Deref<Target = [Iptr]>,
25{
26    storage: Storage,
27}
28
29pub type IndPtr<Iptr> = IndPtrBase<Iptr, Vec<Iptr>>;
30pub type IndPtrView<'a, Iptr> = IndPtrBase<Iptr, &'a [Iptr]>;
31
32impl<Iptr, Storage> IndPtrBase<Iptr, Storage>
33where
34    Iptr: SpIndex,
35    Storage: Deref<Target = [Iptr]>,
36{
37    pub(crate) fn check_structure(
38        storage: &Storage,
39    ) -> Result<(), StructureError> {
40        for i in storage.iter() {
41            if i.try_index().is_none() {
42                return Err(StructureError::OutOfRange(
43                    "Indptr value out of range of usize",
44                ));
45            }
46        }
47        if !storage
48            .windows(2)
49            .all(|x| x[0].index_unchecked() <= x[1].index_unchecked())
50        {
51            return Err(StructureError::Unsorted("Unsorted indptr"));
52        }
53        if storage
54            .last()
55            .copied()
56            .map(Iptr::index_unchecked)
57            .is_some_and(|i| i > usize::MAX / 2)
58        {
59            // We do not allow indptr values to be larger than half
60            // the maximum value of an usize, as that would clearly exhaust
61            // all available memory
62            // This means we could have an isize, but in practice it's
63            // easier to work with usize for indexing.
64            return Err(StructureError::OutOfRange(
65                "An indptr value is larger than allowed",
66            ));
67        }
68        if storage.len() == 0 {
69            // An empty matrix has an inptr of size 1
70            return Err(StructureError::SizeMismatch(
71                "An indptr should have its len >= 1",
72            ));
73        }
74        Ok(())
75    }
76
77    pub fn new_checked(
78        storage: Storage,
79    ) -> Result<Self, (Storage, StructureError)> {
80        match Self::check_structure(&storage) {
81            Ok(_) => Ok(Self::new_trusted(storage)),
82            Err(e) => Err((storage, e)),
83        }
84    }
85
86    pub(crate) fn new_trusted(storage: Storage) -> Self {
87        Self { storage }
88    }
89
90    pub fn view(&self) -> IndPtrView<'_, Iptr> {
91        IndPtrView {
92            storage: &self.storage[..],
93        }
94    }
95
96    /// The length of the underlying storage
97    pub fn len(&self) -> usize {
98        self.storage.len()
99    }
100
101    /// Tests whether this indptr is empty
102    pub fn is_empty(&self) -> bool {
103        // An indptr of len 0 is nonsensical, we should treat that as empty
104        // but fail on debug
105        debug_assert!(self.storage.len() != 0);
106        self.storage.len() <= 1
107    }
108
109    /// The number of outer dimensions this indptr represents
110    pub fn outer_dims(&self) -> usize {
111        if self.storage.len() >= 1 {
112            self.storage.len() - 1
113        } else {
114            0
115        }
116    }
117
118    /// Indicates whether the underlying storage is proper, which means the
119    /// indptr corresponds to a non-sliced matrix.
120    ///
121    /// An empty matrix is considered non-proper.
122    pub fn is_proper(&self) -> bool {
123        self.storage.first().is_some_and(|i| *i == Iptr::zero())
124    }
125
126    /// Return a view on the underlying slice if it is a proper `indptr` slice,
127    /// which is the case if its first element is 0. `None` will be returned
128    /// otherwise.
129    pub fn as_slice(&self) -> Option<&[Iptr]> {
130        if self.is_proper() {
131            Some(&self.storage[..])
132        } else {
133            None
134        }
135    }
136
137    /// Return a view of the underlying storage. Should be used with care in
138    /// sparse algorithms, as this won't check if the storage corresponds to a
139    /// proper matrix
140    pub fn raw_storage(&self) -> &[Iptr] {
141        &self.storage[..]
142    }
143
144    /// Return a view of the underlying storage. Should only be used with
145    /// subsequent structure checks.
146    pub(crate) fn raw_storage_mut(&mut self) -> &mut [Iptr]
147    where
148        Storage: DerefMut<Target = [Iptr]>,
149    {
150        &mut self.storage[..]
151    }
152
153    /// Consume `self` and return the underlying storage
154    pub fn into_raw_storage(self) -> Storage {
155        self.storage
156    }
157
158    pub fn to_owned(&self) -> IndPtr<Iptr> {
159        IndPtr {
160            storage: self.storage.to_vec(),
161        }
162    }
163
164    /// Returns a proper indptr representation, cloning if we do not have
165    /// a proper indptr.
166    ///
167    /// # Warning
168    ///
169    /// For ffi usage, one needs to call `Cow::as_ptr`, but it's important
170    /// to keep the `Cow` alive during the lifetime of the pointer. Example
171    /// of a correct and incorrect ffi usage:
172    ///
173    /// ```rust
174    /// let mat: sprs::CsMat<f64> = sprs::CsMat::eye(5);
175    /// let mid = mat.view().middle_outer_views(1, 2);
176    /// let ptr = {
177    ///     let indptr = mid.indptr(); // needed for lifetime
178    ///     let indptr_proper = indptr.to_proper();
179    ///     println!(
180    ///         "ptr {:?} is valid as long as _indptr_proper_owned is in scope",
181    ///         indptr_proper.as_ptr()
182    ///     );
183    ///     indptr_proper.as_ptr()
184    /// };
185    /// // This line is UB.
186    /// // println!("ptr deref: {}", *ptr);
187    /// ```
188    ///
189    /// It is much easier to directly use the `proper_indptr` method of
190    /// `CsMatBase` directly:
191    ///
192    /// ```rust
193    /// let mat: sprs::CsMat<f64> = sprs::CsMat::eye(5);
194    /// let mid = mat.view().middle_outer_views(1, 2);
195    /// let ptr = {
196    ///     let indptr_proper = mid.proper_indptr();
197    ///     println!(
198    ///         "ptr {:?} is valid as long as _indptr_proper_owned is in scope",
199    ///         indptr_proper.as_ptr()
200    ///     );
201    ///     indptr_proper.as_ptr()
202    /// };
203    /// // This line is UB.
204    /// // println!("ptr deref: {}", *ptr);
205    /// ```
206    pub fn to_proper(&self) -> std::borrow::Cow<'_, [Iptr]> {
207        if self.is_proper() {
208            std::borrow::Cow::Borrowed(&self.storage[..])
209        } else {
210            let offset = self.offset();
211            let proper = self.storage.iter().map(|i| *i - offset).collect();
212            std::borrow::Cow::Owned(proper)
213        }
214    }
215
216    fn offset(&self) -> Iptr {
217        let zero = Iptr::zero();
218        self.storage.first().copied().unwrap_or(zero)
219    }
220
221    /// Iterate over the nonzeros represented by this indptr, yielding the
222    /// outer dimension for each nonzero
223    pub fn iter_outer_nnz_inds(
224        &self,
225    ) -> impl std::iter::DoubleEndedIterator<Item = usize>
226           + std::iter::ExactSizeIterator<Item = usize>
227           + '_ {
228        let mut cur_outer = 0;
229        (0..self.nnz()).map(move |i| {
230            // loop to find the correct outer dimension. Looping
231            // is necessary because there can be several adjacent
232            // empty outer dimensions.
233            loop {
234                let nnz_end = self.outer_inds_sz(cur_outer).end;
235                if i == nnz_end {
236                    cur_outer += 1;
237                } else {
238                    break;
239                }
240            }
241            cur_outer
242        })
243    }
244
245    /// Iterate over outer dimensions, yielding start and end indices for each
246    /// outer dimension.
247    pub fn iter_outer(
248        &self,
249    ) -> impl std::iter::DoubleEndedIterator<Item = Range<Iptr>>
250           + std::iter::ExactSizeIterator<Item = Range<Iptr>>
251           + '_ {
252        let offset = self.offset();
253        self.storage.windows(2).map(move |x| {
254            if offset == Iptr::zero() {
255                x[0]..x[1]
256            } else {
257                (x[0] - offset)..(x[1] - offset)
258            }
259        })
260    }
261
262    /// Iterate over outer dimensions, yielding start and end indices for each
263    /// outer dimension.
264    ///
265    /// Returns a range of usize to ensure iteration of indices and data is easy
266    pub fn iter_outer_sz(
267        &self,
268    ) -> impl std::iter::DoubleEndedIterator<Item = Range<usize>>
269           + std::iter::ExactSizeIterator<Item = Range<usize>>
270           + '_ {
271        self.iter_outer().map(|range| {
272            range.start.index_unchecked()..range.end.index_unchecked()
273        })
274    }
275
276    /// Return the value of the indptr at index i. This method is intended for
277    /// low-level usage only, `outer_inds` should be preferred most of the time
278    pub fn index(&self, i: usize) -> Iptr {
279        let offset = self.offset();
280        self.storage[i] - offset
281    }
282
283    /// Get the start and end indices for the requested outer dimension
284    ///
285    /// # Panics
286    ///
287    /// If `i >= self.outer_dims()`
288    pub fn outer_inds(&self, i: usize) -> Range<Iptr> {
289        assert!(i + 1 < self.storage.len());
290        let offset = self.offset();
291        (self.storage[i] - offset)..(self.storage[i + 1] - offset)
292    }
293
294    /// Get the start and end indices for the requested outer dimension
295    ///
296    /// Returns a range of usize to ensure iteration of indices and data is easy
297    ///
298    /// # Panics
299    ///
300    /// If `i >= self.outer_dims()`
301    pub fn outer_inds_sz(&self, i: usize) -> Range<usize> {
302        let range = self.outer_inds(i);
303        range.start.index_unchecked()..range.end.index_unchecked()
304    }
305
306    /// Get the number of nonzeros in the requested outer dimension
307    ///
308    /// # Panics
309    ///
310    /// If `i >= self.outer_dims()`
311    pub fn nnz_in_outer(&self, i: usize) -> Iptr {
312        assert!(i + 1 < self.storage.len());
313        self.storage[i + 1] - self.storage[i]
314    }
315
316    /// Get the number of nonzeros in the requested outer dimension
317    ///
318    /// Returns a usize
319    ///
320    /// # Panics
321    ///
322    /// If `i >= self.outer_dims()`
323    pub fn nnz_in_outer_sz(&self, i: usize) -> usize {
324        self.nnz_in_outer(i).index_unchecked()
325    }
326
327    /// Get the start and end indices for the requested outer dimension slice
328    ///
329    /// # Panics
330    ///
331    /// If `start >= self.outer_dims() || end > self.outer_dims()`
332    pub fn outer_inds_slice(&self, start: usize, end: usize) -> Range<usize> {
333        let off = self.offset();
334        let range = (self.storage[start] - off)..(self.storage[end] - off);
335        range.start.index_unchecked()..range.end.index_unchecked()
336    }
337
338    /// The number of nonzero elements described by this indptr
339    pub fn nnz(&self) -> usize {
340        let offset = self.offset();
341        // index_unchecked validity: structure checks ensure that the last index
342        // larger than the first, and that both can be represented as an usize
343        self.storage
344            .last()
345            .map(|i| *i - offset)
346            .map_or(0, Iptr::index_unchecked)
347    }
348
349    /// The number of nonzero elements described by this indptr, using the
350    /// actual storage type
351    pub fn nnz_i(&self) -> Iptr {
352        let offset = self.offset();
353        let zero = Iptr::zero();
354        // index_unchecked validity: structure checks ensure that the last index
355        // larger than the first, and that both can be represented as an usize
356        self.storage.last().map_or(zero, |i| *i - offset)
357    }
358
359    /// Slice this indptr to include only the outer dimensions in the range
360    /// `start..end`.
361    pub(crate) fn middle_slice(
362        &self,
363        range: impl crate::range::Range,
364    ) -> IndPtrView<'_, Iptr> {
365        self.view().middle_slice_rbr(range)
366    }
367}
368
369impl<Iptr: SpIndex> IndPtr<Iptr> {
370    /// Reserve storage in the underlying vector
371    pub(crate) fn reserve(&mut self, cap: usize) {
372        self.storage.reserve(cap);
373    }
374
375    /// Reserve storage in the underlying vector
376    pub(crate) fn reserve_exact(&mut self, cap: usize) {
377        self.storage.reserve_exact(cap);
378    }
379
380    /// Push to the underlying vector. Assumes the structure will be respected,
381    /// no checks are performed (thus the crate-only visibility).
382    pub(crate) fn push(&mut self, elem: Iptr) {
383        self.storage.push(elem);
384    }
385
386    /// Resize the underlying vector. Assumes the structure will be respected,
387    /// no checks are performed (thus the crate-only visibility). It's probable
388    /// additional modifications need to be performed to guarantee integrity.
389    pub(crate) fn resize(&mut self, new_len: usize, value: Iptr) {
390        self.storage.resize(new_len, value);
391    }
392
393    /// Increment the indptr values to record that an element has been added
394    /// to the indices and data, for the outer dimension `outer_dim`.
395    pub(crate) fn record_new_element(&mut self, outer_ind: usize) {
396        for val in self.storage[outer_ind + 1..].iter_mut() {
397            *val += Iptr::one();
398        }
399    }
400}
401
402impl<'a, Iptr: SpIndex> IndPtrView<'a, Iptr> {
403    /// Slice this indptr to include only the outer dimensions in the range
404    /// `start..end`. Reborrows to get the actual lifetime of the data wrapped
405    /// in this view
406    pub(crate) fn middle_slice_rbr(
407        &self,
408        range: impl crate::range::Range,
409    ) -> IndPtrView<'a, Iptr> {
410        let start = range.start();
411        let end = range.end().unwrap_or_else(|| self.outer_dims());
412        IndPtrView {
413            storage: &self.storage[start..=end],
414        }
415    }
416
417    /// Reborrow this view to get the lifetime of the underlying slice
418    pub(crate) fn reborrow(&self) -> IndPtrView<'a, Iptr> {
419        IndPtrView {
420            storage: &self.storage[..],
421        }
422    }
423}
424
425/// Allows comparison to vectors and slices
426impl<Iptr: SpIndex, IptrStorage, IptrStorage2> std::cmp::PartialEq<IptrStorage2>
427    for IndPtrBase<Iptr, IptrStorage>
428where
429    IptrStorage: Deref<Target = [Iptr]>,
430    IptrStorage2: Deref<Target = [Iptr]>,
431{
432    fn eq(&self, other: &IptrStorage2) -> bool {
433        self.raw_storage() == &**other
434    }
435}
436
437#[cfg(test)]
438mod tests {
439    use super::{IndPtr, IndPtrView};
440
441    #[test]
442    fn constructors() {
443        let raw_valid = vec![0, 1, 2, 3];
444        assert!(IndPtr::new_checked(raw_valid).is_ok());
445        let raw_valid = vec![0, 1, 2, 3];
446        assert!(IndPtrView::new_checked(&raw_valid).is_ok());
447        // Indptr for an empty matrix
448        let raw_valid = vec![0];
449        assert!(IndPtrView::new_checked(&raw_valid).is_ok());
450        // Indptr for an empty matrix view
451        let raw_valid = vec![1];
452        assert!(IndPtrView::new_checked(&raw_valid).is_ok());
453
454        let raw_invalid = &[0, 2, 1];
455        assert_eq!(
456            IndPtrView::new_checked(raw_invalid)
457                .map_err(|(_, e)| e.kind())
458                .unwrap_err(),
459            crate::errors::StructureErrorKind::Unsorted
460        );
461        let raw_invalid: &[usize] = &[];
462        assert!(IndPtrView::new_checked(raw_invalid).is_err());
463    }
464
465    #[test]
466    fn empty() {
467        assert!(IndPtrView::new_checked(&[0]).unwrap().is_empty());
468        assert!(!IndPtrView::new_checked(&[0, 1]).unwrap().is_empty());
469        #[cfg(debug_assertions)]
470        {
471            assert!(IndPtrView::new_trusted(&[0]).is_empty());
472        }
473        #[cfg(not(debug_assertions))]
474        {
475            assert!(IndPtrView::new_trusted(&[0]).is_empty());
476        }
477    }
478
479    #[test]
480    fn outer_dims() {
481        assert_eq!(IndPtrView::new_checked(&[0]).unwrap().outer_dims(), 0);
482        assert_eq!(IndPtrView::new_checked(&[0, 1]).unwrap().outer_dims(), 1);
483        assert_eq!(
484            IndPtrView::new_checked(&[2, 3, 5, 7]).unwrap().outer_dims(),
485            3
486        );
487    }
488
489    #[test]
490    fn is_proper() {
491        assert!(IndPtrView::new_checked(&[0, 1]).unwrap().is_proper());
492        assert!(!IndPtrView::new_checked(&[1, 2]).unwrap().is_proper());
493    }
494
495    #[test]
496    fn offset() {
497        assert_eq!(IndPtrView::new_checked(&[0, 1]).unwrap().offset(), 0);
498        assert_eq!(IndPtrView::new_checked(&[1, 2]).unwrap().offset(), 1);
499    }
500
501    #[test]
502    fn nnz() {
503        assert_eq!(IndPtrView::new_checked(&[0, 1]).unwrap().nnz(), 1);
504        assert_eq!(IndPtrView::new_checked(&[1, 2]).unwrap().nnz(), 1);
505    }
506
507    #[test]
508    fn outer_inds() {
509        let iptr = IndPtrView::new_checked(&[0, 1, 3, 8]).unwrap();
510        assert_eq!(iptr.outer_inds(0), 0..1);
511        assert_eq!(iptr.outer_inds(1), 1..3);
512        assert_eq!(iptr.outer_inds(2), 3..8);
513        let res = std::panic::catch_unwind(|| iptr.outer_inds(3));
514        assert!(res.is_err());
515    }
516
517    #[test]
518    fn nnz_in_outer() {
519        let iptr = IndPtrView::new_checked(&[0, 1, 3, 8]).unwrap();
520        assert_eq!(iptr.nnz_in_outer(0), 1);
521        assert_eq!(iptr.nnz_in_outer(1), 2);
522        assert_eq!(iptr.nnz_in_outer(2), 5);
523    }
524
525    #[test]
526    fn outer_inds_slice() {
527        let iptr = IndPtrView::new_checked(&[0, 1, 3, 8]).unwrap();
528        assert_eq!(iptr.outer_inds_slice(0, 1), 0..1);
529        assert_eq!(iptr.outer_inds_slice(0, 2), 0..3);
530        assert_eq!(iptr.outer_inds_slice(1, 3), 1..8);
531        let res = std::panic::catch_unwind(|| iptr.outer_inds_slice(3, 4));
532        assert!(res.is_err());
533    }
534
535    #[test]
536    fn iter_outer() {
537        let iptr = IndPtrView::new_checked(&[0, 1, 3, 8]).unwrap();
538        let mut iter = iptr.iter_outer();
539        assert_eq!(iter.next().unwrap(), 0..1);
540        assert_eq!(iter.next().unwrap(), 1..3);
541        assert_eq!(iter.next().unwrap(), 3..8);
542        assert!(iter.next().is_none());
543    }
544
545    #[test]
546    fn iter_outer_nnz_inds() {
547        let iptr = IndPtrView::new_checked(&[0, 1, 3, 8]).unwrap();
548        let mut iter = iptr.iter_outer_nnz_inds();
549        assert_eq!(iter.next().unwrap(), 0);
550        assert_eq!(iter.next().unwrap(), 1);
551        assert_eq!(iter.next().unwrap(), 1);
552        assert_eq!(iter.next().unwrap(), 2);
553        assert_eq!(iter.next().unwrap(), 2);
554        assert_eq!(iter.next().unwrap(), 2);
555        assert_eq!(iter.next().unwrap(), 2);
556        assert_eq!(iter.next().unwrap(), 2);
557        assert!(iter.next().is_none());
558    }
559
560    #[test]
561    fn compare_with_slices() {
562        let iptr = IndPtrView::new_checked(&[0, 1, 3, 8]).unwrap();
563        assert!(iptr == &[0, 1, 3, 8][..]);
564        assert!(iptr == vec![0, 1, 3, 8]);
565        let iptr = IndPtrView::new_checked(&[1, 1, 3, 8]).unwrap();
566        assert!(iptr == &[1, 1, 3, 8][..]);
567    }
568}