algebra_sparse/
diagonal.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 na::{DMatrix, DMatrixView, RealField};
16
17use crate::traits::IntoView;
18
19/// A sparse matrix composed of block diagonal matrices.
20///
21/// This structure efficiently stores matrices that have a block diagonal structure,
22/// where non-zero elements are only present in square blocks along the main diagonal.
23/// This is commonly found in physical simulations, finite element methods, and
24/// systems with localized interactions.
25///
26/// # Format
27///
28/// The matrix stores all block data in three arrays:
29/// - `values`: All block elements stored contiguously in column-major order
30/// - `block_row_offsets`: Starting row index for each block
31/// - `block_element_offsets`: Starting index in `values` array for each block
32///
33/// # Examples
34///
35/// ```rust
36/// use algebra_sparse::DiagonalBlockMatrix;
37///
38/// // Create a block diagonal matrix with blocks of size 2 and 3
39/// // 2x2 block [1 2; 3 4] stored as [1, 3, 2, 4] in column-major
40/// // 3x3 block [5 6 7; 8 9 10; 11 12 13] stored as [5, 8, 11, 6, 9, 12, 7, 10, 13]
41/// let block_values = vec![1.0, 3.0, 2.0, 4.0,  // 2x2 block
42///                        5.0, 8.0, 11.0, 6.0, 9.0, 12.0, 7.0, 10.0, 13.0]; // 3x3 block
43/// let block_sizes = [2, 3];
44/// let matrix = DiagonalBlockMatrix::from_block_values(block_values, &block_sizes);
45///
46/// println!("Matrix shape: {}x{}", matrix.nrows(), matrix.ncols());
47/// ```
48pub struct DiagonalBlockMatrix<T> {
49    /// All values for matrix blocks stored in a single vector.
50    values: Vec<T>,
51    /// Row offset for each block in the matrix. Size = `num_blocks` + 1.
52    /// One extra element makes it easier to calculate the size of the last block.
53    block_row_offsets: Vec<usize>,
54    /// The offsets of the first element in the `values` array for each block.
55    block_element_offsets: Vec<usize>,
56}
57
58impl<T> DiagonalBlockMatrix<T> {
59    /// Creates a block diagonal matrix from block values and sizes.
60    ///
61    /// # Arguments
62    /// * `values` - All block elements stored contiguously in column-major order
63    /// * `block_sizes` - Size of each square block along the diagonal
64    ///
65    /// # Examples
66    ///
67    /// ```rust
68    /// use algebra_sparse::DiagonalBlockMatrix;
69    ///
70    /// // Create blocks: [1 2; 3 4] and [5 6 7; 8 9 10; 11 12 13]
71    /// // Stored in column-major order: [1, 3, 2, 4] for first block
72    /// let values = vec![1.0, 3.0, 2.0, 4.0, 5.0, 8.0, 11.0, 6.0, 9.0, 12.0, 7.0, 10.0, 13.0];
73    /// let block_sizes = [2, 3]; // 2x2 block and 3x3 block
74    /// let matrix = DiagonalBlockMatrix::from_block_values(values, &block_sizes);
75    /// ```
76    #[inline]
77    pub fn from_block_values(values: Vec<T>, block_sizes: &[usize]) -> Self {
78        let mut block_row_offsets = Vec::with_capacity(block_sizes.len() + 1);
79        let mut block_element_offsets = Vec::with_capacity(block_sizes.len() + 1);
80        let mut block_row_offset = 0;
81        let mut block_element_offset = 0;
82
83        for size in block_sizes.iter().copied() {
84            block_row_offsets.push(block_row_offset);
85            block_element_offsets.push(block_element_offset);
86            block_row_offset += size;
87            block_element_offset += size * size;
88        }
89        block_row_offsets.push(block_row_offset);
90        block_element_offsets.push(block_element_offset);
91
92        Self {
93            values,
94            block_row_offsets,
95            block_element_offsets,
96        }
97    }
98
99    #[inline]
100    pub fn nrows(&self) -> usize {
101        self.block_row_offsets.last().copied().unwrap_or(0)
102    }
103
104    #[inline]
105    pub fn ncols(&self) -> usize {
106        self.nrows()
107    }
108}
109
110impl<'a, T> IntoView for &'a DiagonalBlockMatrix<T> {
111    type View = DiagonalBlockMatrixView<'a, T>;
112
113    #[inline]
114    fn into_view(self) -> Self::View {
115        DiagonalBlockMatrixView {
116            values: &self.values,
117            block_element_offsets: &self.block_element_offsets,
118            block_row_offsets: &self.block_row_offsets,
119        }
120    }
121}
122
123/// An immutable view of a block diagonal matrix.
124///
125/// This provides efficient read-only access to block diagonal matrix data without allocation.
126/// Views are commonly used for matrix operations and can be easily created from
127/// block diagonal matrices.
128///
129/// # Examples
130///
131/// ```rust
132/// use algebra_sparse::DiagonalBlockMatrix;
133/// use algebra_sparse::traits::IntoView;
134///
135/// let matrix = DiagonalBlockMatrix::from_block_values(vec![1.0, 2.0, 3.0, 4.0], &[2, 2]);
136/// let view = matrix.into_view();
137///
138/// println!("Number of blocks: {}", view.num_blocks());
139/// println!("Matrix shape: {}x{}", view.nrows(), view.ncols());
140/// ```
141#[derive(Clone, Copy)]
142pub struct DiagonalBlockMatrixView<'a, T> {
143    /// All block elements stored contiguously in column-major order.
144    values: &'a [T],
145    /// Row offset for each block in the matrix.
146    block_row_offsets: &'a [usize],
147    /// The offsets of the first element in the `values` array for each block.
148    block_element_offsets: &'a [usize],
149}
150
151impl<'a, T> DiagonalBlockMatrixView<'a, T>
152where
153    T: RealField,
154{
155    /// Creates a block diagonal matrix view from raw parts without checking validity.
156    ///
157    /// # Safety
158    /// This function does not validate that the provided parts form a valid block diagonal matrix.
159    /// Invalid parts may cause undefined behavior when accessing the matrix.
160    ///
161    /// # Arguments
162    /// * `values` - All block elements stored contiguously
163    /// * `block_row_offsets` - Row offset array for each block
164    /// * `block_element_offsets` - Element offset array for each block
165    #[inline]
166    pub fn from_parts_unchecked(
167        values: &'a [T],
168        block_row_offsets: &'a [usize],
169        block_element_offsets: &'a [usize],
170    ) -> Self {
171        let slf = Self {
172            values,
173            block_row_offsets,
174            block_element_offsets,
175        };
176        #[cfg(debug_assertions)]
177        slf.assert_valid();
178        slf
179    }
180
181    #[cfg(debug_assertions)]
182    fn assert_valid(&self) {
183        let mut expected_num_values = 0;
184        let num_blocks = self.num_blocks();
185        for block_index in 0..num_blocks {
186            assert!(
187                self.block_row_offsets[block_index] < self.block_row_offsets[block_index + 1],
188                "Block sizes must be positive"
189            );
190            let block_size =
191                self.block_row_offsets[block_index + 1] - self.block_row_offsets[block_index];
192
193            let num_block_elements = self.block_element_offsets[block_index + 1]
194                - self.block_element_offsets[block_index];
195            assert!(
196                num_block_elements == block_size * block_size,
197                "Block element offsets do not match the expected size based on block sizes, expected {}, got {}, block_index = {}",
198                block_size * block_size,
199                num_block_elements,
200                block_index
201            );
202
203            expected_num_values += block_size * block_size;
204        }
205
206        assert!(
207            expected_num_values == self.values.len(),
208            "Number of values does not match the expected number based on block sizes , expected {}, got {}",
209            expected_num_values,
210            self.values.len()
211        );
212    }
213
214    #[inline]
215    pub fn values(&self) -> &[T] {
216        self.values
217    }
218
219    #[inline]
220    pub fn num_blocks(&self) -> usize {
221        self.block_row_offsets.len() - 1
222    }
223
224    #[inline]
225    pub fn nrows(&self) -> usize {
226        self.block_row_offsets.last().copied().unwrap_or(0)
227    }
228
229    #[inline]
230    pub fn ncols(&self) -> usize {
231        self.nrows()
232    }
233
234    /// Get the size of a block
235    ///
236    /// # Panics
237    ///
238    /// Panics if the `block_index` is out of bounds.
239    #[inline]
240    pub fn get_block_size(&self, block_index: usize) -> usize {
241        debug_assert!(
242            (block_index < self.num_blocks()),
243            "Block index out of bounds"
244        );
245        self.block_row_offsets[block_index + 1] - self.block_row_offsets[block_index]
246    }
247
248    #[inline]
249    pub fn get_block_row_start(&self, block_index: usize) -> usize {
250        self.block_row_offsets[block_index]
251    }
252
253    /// Get the range of rows for the given block index.
254    #[inline]
255    pub fn get_block_row_range(&self, block_index: usize) -> std::ops::Range<usize> {
256        debug_assert!(
257            (block_index < self.num_blocks()),
258            "Block index out of bounds"
259        );
260        let start = self.block_row_offsets[block_index];
261        let end = self.block_row_offsets[block_index + 1];
262        start..end
263    }
264
265    /// Get the block matrix at the given index as a view.
266    ///
267    /// # Panics
268    ///
269    /// Panics if the `block_index` is out of bounds.
270    pub fn view_block(&self, block_index: usize) -> DMatrixView<T> {
271        debug_assert!(
272            (block_index < self.num_blocks()),
273            "Block index out of bounds"
274        );
275        let start = self.block_element_offsets[block_index];
276        let end = self.block_element_offsets[block_index + 1];
277        let size = self.block_row_offsets[block_index + 1] - self.block_row_offsets[block_index];
278        debug_assert!(size > 0);
279        DMatrixView::from_slice(&self.values[start..end], size, size)
280    }
281
282    pub fn to_dense(&self) -> DMatrix<T> {
283        let nrows = self.nrows();
284        let ncols = self.ncols();
285        let mut dense = DMatrix::zeros(nrows, ncols);
286        for b_idx in 0..self.num_blocks() {
287            let block = self.view_block(b_idx);
288            let range = self.get_block_row_range(b_idx);
289            let mut dst = dense.view_range_mut(range.clone(), range);
290            dst.copy_from(&block);
291        }
292        dense
293    }
294}