algebra_sparse/
csv.rs

1// Copyright (C) 2020-2025 algebra-sparse authors. All Rights Reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use crate::{CsMatrix, Real};
16
17/// An immutable view of a sparse vector.
18///
19/// This provides efficient read-only access to sparse vector data without allocation.
20/// Sparse vectors store only non-zero elements and their indices, making them
21/// memory-efficient for vectors with few non-zero elements.
22///
23/// # Format
24///
25/// The sparse vector uses two parallel arrays:
26/// - `indices`: Indices of non-zero elements
27/// - `values`: Non-zero values corresponding to each index
28///
29/// # Examples
30///
31/// ```rust
32/// use algebra_sparse::CsVecRef;
33///
34/// let indices = [0, 2, 4];
35/// let values = [1.0, 3.0, 5.0];
36/// let sparse_vec = CsVecRef::from_parts_unchecked(&indices, &values, 6);
37///
38/// // Iterate over non-zero elements
39/// for (index, value) in sparse_vec.iter() {
40///     println!("vec[{}] = {}", index, value);
41/// }
42/// ```
43#[non_exhaustive]
44#[derive(Clone, Copy)]
45pub struct CsVecRef<'a, T> {
46    /// The indices of non-zero elements.
47    indices: &'a [usize],
48    /// The non-zero values corresponding to each index.
49    values: &'a [T],
50    /// The total length of the vector (including zero elements).
51    ///
52    /// # Note
53    ///
54    /// This is not the length of `values`, but the full vector length.
55    len: usize,
56}
57
58impl<'a, T> CsVecRef<'a, T> {
59    /// Creates a `CsVecRef` from raw parts without checking validity.
60    ///
61    /// # Safety
62    /// This function does not validate that the provided parts form a valid sparse vector.
63    /// The `indices` and `values` arrays should have the same length, and indices should be
64    /// in ascending order and within bounds [0, len).
65    ///
66    /// # Arguments
67    /// * `indices` - Indices of non-zero elements
68    /// * `values` - Non-zero values corresponding to each index
69    /// * `len` - Total length of the vector
70    #[inline]
71    pub fn from_parts_unchecked(indices: &'a [usize], values: &'a [T], len: usize) -> Self {
72        Self {
73            indices,
74            values,
75            len,
76        }
77    }
78
79    /// Returns an iterator over the non-zero elements of the vector.
80    ///
81    /// The iterator yields tuples of (index, value) for each non-zero element.
82    ///
83    /// # Returns
84    /// An iterator over (index, value) pairs
85    #[inline]
86    pub fn iter(&self) -> impl Iterator<Item = (usize, T)>
87    where
88        T: Copy,
89    {
90        self.indices
91            .iter()
92            .copied()
93            .zip(self.values.iter().copied())
94    }
95
96    /// Returns the total length of the vector.
97    ///
98    /// This is the full length of the vector, not the number of non-zero elements.
99    ///
100    /// # Returns
101    /// The total length including zero elements
102    #[inline]
103    pub fn len(&self) -> usize {
104        self.len
105    }
106
107    /// Returns true if the vector is empty (has length 0).
108    #[inline]
109    pub fn is_empty(&self) -> bool {
110        self.len == 0
111    }
112
113    /// Returns the indices of the non-zero elements.
114    ///
115    /// # Returns
116    /// A slice of indices for non-zero elements
117    #[inline]
118    pub fn indices(&self) -> &'a [usize] {
119        self.indices
120    }
121
122    /// Returns all non-zero values in the vector.
123    ///
124    /// # Returns
125    /// A slice of non-zero values
126    #[inline]
127    pub fn values(&self) -> &'a [T] {
128        self.values
129    }
130}
131
132/// A mutable view of a sparse vector.
133///
134/// This provides mutable access to the values of a sparse vector while keeping
135/// the structure (indices) immutable. This is useful for modifying existing
136/// non-zero elements without changing the sparsity pattern.
137///
138/// # Note
139///
140/// Only the values can be modified. The indices and structure cannot be changed
141/// through this type. To change the structure, you need to use a `CsVecBuilder`.
142pub struct CsVecMut<'a, T> {
143    /// The indices of non-zero elements (immutable).
144    pub col_indices: &'a [usize],
145    /// The non-zero values (mutable).
146    pub values: &'a mut [T],
147}
148
149/// A builder for constructing sparse vectors efficiently.
150///
151/// This builder allows efficient construction of sparse vectors by adding elements
152/// in order. When the builder is dropped, the vector is automatically finalized
153/// and added to the parent matrix.
154///
155/// # Features
156///
157/// - Automatic filtering of values below zero threshold
158/// - Validation of index ordering (must be in ascending order)
159/// - Efficient batch operations
160/// - Automatic finalization when dropped
161///
162/// # Examples
163///
164/// ```rust
165/// use algebra_sparse::{CsMatrix, CsVecBuilder};
166/// use nalgebra::DMatrix;
167///
168/// let mut matrix = CsMatrix::new(3);
169/// {
170///     let mut builder = matrix.new_lane_builder(1e-10);
171///     builder.push(0, 1.0);
172///     builder.push(2, 2.0);
173///     // Vector is automatically added when builder goes out of scope
174/// }
175/// ```
176pub struct CsVecBuilder<'a, T> {
177    secondary_indices: &'a mut Vec<usize>,
178    primary_offsets: &'a mut Vec<usize>,
179    values: &'a mut Vec<T>,
180    mat_value_start: usize,
181    num_nonzero_values: usize,
182    zero_threshold: T,
183}
184
185impl<'a, T> CsVecBuilder<'a, T>
186where
187    T: Real,
188{
189    /// Creates a new sparse vector builder for the given matrix.
190    ///
191    /// When the builder is dropped, a new lane will be added to the sparse matrix
192    /// if the builder contains any non-zero elements.
193    ///
194    /// # Arguments
195    /// * `m` - A mutable reference to the sparse matrix
196    /// * `zero_threshold` - The threshold below which values are considered zero
197    #[inline]
198    pub fn new(m: &'a mut CsMatrix<T>, zero_threshold: T) -> Self {
199        Self {
200            secondary_indices: &mut m.secondary_indices,
201            primary_offsets: &mut m.primary_offsets,
202            values: &mut m.values,
203            mat_value_start: 0,
204            num_nonzero_values: 0,
205            zero_threshold,
206        }
207    }
208
209    /// Creates a builder from raw parts without checking validity.
210    ///
211    /// # Safety
212    /// This function does not validate the provided parts. Use with caution.
213    pub fn from_parts_unchecked(
214        secondary_indices: &'a mut Vec<usize>,
215        primary_offsets: &'a mut Vec<usize>,
216        values: &'a mut Vec<T>,
217        mat_value_start: usize,
218        zero_threshold: T,
219    ) -> Self {
220        Self {
221            secondary_indices,
222            primary_offsets,
223            values,
224            mat_value_start,
225            zero_threshold,
226            num_nonzero_values: 0,
227        }
228    }
229
230    /// Pushes a value to the vector without checking if it is zero or if the index is in order.
231    ///
232    /// # Safety
233    /// This function bypasses validation checks. Ensure that:
234    /// - The value is not zero (above threshold)
235    /// - The index is in ascending order
236    /// - The index is within valid bounds
237    ///
238    /// # Arguments
239    /// * `index` - Index of the element
240    /// * `value` - Value to add
241    #[inline]
242    pub fn push_unchecked(&mut self, index: usize, value: T) {
243        #[cfg(debug_assertions)]
244        {
245            self.validate(index, value);
246        }
247        self.secondary_indices.push(index);
248        self.values.push(value);
249        self.num_nonzero_values += 1;
250    }
251
252    /// Pushes a value to the vector with validation.
253    ///
254    /// # Arguments
255    /// * `col` - Column index of the element
256    /// * `value` - Value to add
257    ///
258    /// # Panics
259    ///
260    /// Panics if:
261    /// - The value is zero (below threshold)
262    /// - The column index is not in ascending order
263    #[inline]
264    pub fn push(&mut self, col: usize, value: T) {
265        self.validate(col, value);
266        self.push_unchecked(col, value);
267    }
268
269    /// Pushes multiple values to the vector with validation.
270    ///
271    /// # Arguments
272    /// * `idx_values` - Iterator of (index, value) pairs
273    ///
274    /// # Panics
275    ///
276    /// Panics if any of the values are zero or if the indices are not in ascending order.
277    pub fn extend(&mut self, idx_values: impl IntoIterator<Item = (usize, T)>) {
278        for (col, value) in idx_values {
279            self.push(col, value);
280        }
281    }
282
283    /// Pushes multiple values to the vector without validation.
284    ///
285    /// # Safety
286    /// This function bypasses validation checks.
287    ///
288    /// # Arguments
289    /// * `idx_values` - Iterator of (index, value) pairs
290    pub fn extend_unchecked(&mut self, idx_values: impl IntoIterator<Item = (usize, T)>) {
291        for (col, value) in idx_values {
292            self.push_unchecked(col, value);
293        }
294    }
295
296    /// Pushes all non-zero values to the sparse vector.
297    ///
298    /// This method automatically discards any values that are below the zero threshold.
299    /// This is useful when converting from dense data where many values might be zero.
300    ///
301    /// # Arguments
302    /// * `idx_values` - Iterator of (index, value) pairs
303    #[inline]
304    pub fn extend_with_nonzeros(&mut self, idx_values: impl IntoIterator<Item = (usize, T)>) {
305        for (idx, value) in idx_values {
306            if value.abs() > self.zero_threshold {
307                self.push_unchecked(idx, value);
308            }
309        }
310    }
311
312    /// Finishes the builder and returns the number of non-zero elements.
313    ///
314    /// This method is called automatically when the builder is dropped,
315    /// but can be called explicitly to get the count of non-zero elements.
316    ///
317    /// # Returns
318    /// The number of non-zero elements added
319    ///
320    /// # Note
321    ///
322    /// If the number of non-zero elements is zero, the sparse vector will be discarded
323    /// and not added to the matrix.
324    #[inline]
325    pub fn finish(self) -> usize {
326        self.num_nonzero_values
327    }
328
329    #[inline]
330    fn validate(&self, index: usize, value: T) {
331        assert!(
332            value.abs() > self.zero_threshold,
333            "Cannot push zero value to Sparse Vec"
334        );
335        if self.num_nonzero_values > 0 {
336            if let Some(last) = self.secondary_indices.last() {
337                assert!(
338                    index > *last,
339                    "sparse element indices must be in ascending order"
340                );
341            }
342        }
343    }
344}
345
346/// Automatically finalizes the sparse vector when the builder is dropped.
347///
348/// This implementation ensures that the sparse vector is properly added to the
349/// parent matrix when the builder goes out of scope. If no non-zero elements
350/// were added, the empty lane is discarded.
351impl<T> Drop for CsVecBuilder<'_, T> {
352    fn drop(&mut self) {
353        // Push the row offset
354        if self.num_nonzero_values == 0 {
355            // discard empty lane
356            return;
357        }
358        self.primary_offsets
359            .push(self.values.len() - self.mat_value_start);
360    }
361}