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}