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}