clarabel/algebra/csc/
utils.rs

1//---------------------------------------------------------
2// low-level internal utilities for counting / filling entries
3// in block partitioned sparse matrices.
4//---------------------------------------------------------
5
6use crate::algebra::{CscMatrix, MatrixShape, MatrixTriangle};
7use num_traits::Num;
8use std::iter::zip;
9
10impl<T> CscMatrix<T>
11where
12    T: Num + Copy,
13{
14    // increment self.colptr by the number of nonzeros
15    // in a dense upper/lower triangle on the diagonal.
16    pub(crate) fn colcount_dense_triangle(
17        &mut self,
18        initcol: usize,
19        blockcols: usize,
20        shape: MatrixTriangle,
21    ) {
22        let cols = self.colptr[(initcol)..(initcol + blockcols)].iter_mut();
23        let counts = 1..(blockcols + 1);
24        match shape {
25            MatrixTriangle::Triu => {
26                zip(cols, counts).for_each(|(x, c)| *x += c);
27            }
28
29            MatrixTriangle::Tril => {
30                zip(cols, counts.rev()).for_each(|(x, c)| *x += c);
31            }
32        }
33    }
34
35    // increment the self.colptr by the number of nonzeros
36    // in a square diagonal matrix placed on the diagonal.
37    pub(crate) fn colcount_diag(&mut self, initcol: usize, blockcols: usize) {
38        let cols = self.colptr[initcol..(initcol + blockcols)].iter_mut();
39        cols.for_each(|x| *x += 1);
40    }
41
42    // same as kkt_count_diag, but counts places
43    // where the input matrix M has a missing
44    // diagonal entry.  M must be square and TRIU
45    pub(crate) fn colcount_missing_diag(&mut self, M: &CscMatrix<T>, initcol: usize) {
46        assert_eq!(M.colptr.len(), M.n + 1);
47        assert!(self.colptr.len() >= M.n + initcol);
48
49        for i in 0..M.n {
50            if M.colptr[i] == M.colptr[i+1] ||    // completely empty column
51               M.rowval[M.colptr[i+1]-1] != i
52            // last element is not on diagonal
53            {
54                self.colptr[i + initcol] += 1;
55            }
56        }
57    }
58
59    // increment the self.colptr by the a number of nonzeros.
60    // used to account for the placement of a column
61    // vector that partially populates the column
62    pub(crate) fn colcount_colvec(&mut self, n: usize, _firstrow: usize, firstcol: usize) {
63        // just add the vector length to this column
64        self.colptr[firstcol] += n;
65    }
66
67    // increment the self.colptr by 1 for every element
68    // used to account for the placement of a column
69    // vector that partially populates the column
70    pub(crate) fn colcount_rowvec(&mut self, n: usize, _firstrow: usize, firstcol: usize) {
71        // add one element to each of n consective columns
72        // starting from initcol.  The row index doesn't
73        // matter here.
74        let cols = self.colptr[firstcol..(firstcol + n)].iter_mut();
75        cols.for_each(|x| *x += 1);
76    }
77
78    // increment the self.colptr by the number of nonzeros in M
79
80    pub(crate) fn colcount_block(&mut self, M: &CscMatrix<T>, initcol: usize, shape: MatrixShape) {
81        match shape {
82            MatrixShape::T => {
83                for row in M.rowval.iter() {
84                    self.colptr[initcol + row] += 1;
85                }
86            }
87            MatrixShape::N => {
88                // just add the column count
89                for i in 0..M.n {
90                    self.colptr[initcol + i] += M.colptr[i + 1] - M.colptr[i];
91                }
92            }
93        }
94    }
95
96    //populate a partial column with zeros using the self.colptr as the indicator
97    // the next fill location in each row.
98    pub(crate) fn fill_colvec(&mut self, vtoKKT: &mut [usize], initrow: usize, initcol: usize) {
99        for (i, v) in vtoKKT.iter_mut().enumerate() {
100            let dest = self.colptr[initcol];
101            self.rowval[dest] = initrow + i;
102            self.nzval[dest] = T::zero();
103            *v = dest;
104            self.colptr[initcol] += 1;
105        }
106    }
107
108    // populate a partial row with zeros using the self.colptr as indicator of
109    // next fill location in each row.
110    pub(crate) fn fill_rowvec(&mut self, vtoKKT: &mut [usize], initrow: usize, initcol: usize) {
111        for (i, v) in vtoKKT.iter_mut().enumerate() {
112            let col = initcol + i;
113            let dest = self.colptr[col];
114            self.rowval[dest] = initrow;
115            self.nzval[dest] = T::zero();
116            *v = dest;
117            self.colptr[col] += 1;
118        }
119    }
120
121    // populate values from M using the self.colptr as indicator of
122    // next fill location in each row.
123    #[allow(clippy::needless_range_loop)]
124    pub(crate) fn fill_block(
125        &mut self,
126        M: &CscMatrix<T>,
127        MtoKKT: &mut [usize],
128        initrow: usize,
129        initcol: usize,
130        shape: MatrixShape,
131    ) {
132        for i in 0..M.n {
133            let start = M.colptr[i];
134            let stop = M.colptr[i + 1];
135
136            for j in start..stop {
137                let (col, row);
138
139                match shape {
140                    MatrixShape::T => {
141                        col = M.rowval[j] + initcol;
142                        row = i + initrow;
143                    }
144                    MatrixShape::N => {
145                        col = i + initcol;
146                        row = M.rowval[j] + initrow;
147                    }
148                };
149
150                let dest = self.colptr[col];
151                self.rowval[dest] = row;
152                self.nzval[dest] = M.nzval[j];
153                MtoKKT[j] = dest;
154                self.colptr[col] += 1;
155            }
156        }
157    }
158
159    // Populate the upper or lower triangle with 0s using the self.colptr
160    // as indicator of next fill location in each row
161    pub(crate) fn fill_dense_triangle(
162        &mut self,
163        blocktoKKT: &mut [usize],
164        offset: usize,
165        blockdim: usize,
166        shape: MatrixTriangle,
167    ) {
168        // data will always be supplied as triu, so when filling it into
169        // a tril shape we also need to transpose it.   Just write two
170        // separate functions for clarity here
171
172        match shape {
173            MatrixTriangle::Triu => {
174                self._fill_dense_triangle_triu(blocktoKKT, offset, blockdim);
175            }
176
177            MatrixTriangle::Tril => {
178                self._fill_dense_triangle_tril(blocktoKKT, offset, blockdim);
179            }
180        }
181    }
182
183    pub(crate) fn _fill_dense_triangle_triu(
184        &mut self,
185        blocktoKKT: &mut [usize],
186        offset: usize,
187        blockdim: usize,
188    ) {
189        let mut kidx = 0;
190        for col in offset..(offset + blockdim) {
191            for row in offset..=col {
192                let dest = self.colptr[col];
193                self.rowval[dest] = row;
194                self.nzval[dest] = T::zero(); //structural zero
195                self.colptr[col] += 1;
196                blocktoKKT[kidx] = dest;
197                kidx += 1;
198            }
199        }
200    }
201
202    pub(crate) fn _fill_dense_triangle_tril(
203        &mut self,
204        blocktoKKT: &mut [usize],
205        offset: usize,
206        blockdim: usize,
207    ) {
208        let mut kidx = 0;
209        for row in offset..(offset + blockdim) {
210            for col in offset..=row {
211                let dest = self.colptr[col];
212                self.rowval[dest] = row;
213                self.nzval[dest] = T::zero(); //structural zero
214                self.colptr[col] += 1;
215                blocktoKKT[kidx] = dest;
216                kidx += 1;
217            }
218        }
219    }
220
221    // Populate the diagonal with 0s using the K.colptr as indicator of
222    // next fill location in each row
223    pub(crate) fn fill_diag(&mut self, diagtoKKT: &mut [usize], offset: usize, blockdim: usize) {
224        for (i, col) in (offset..(offset + blockdim)).enumerate() {
225            let dest = self.colptr[col];
226            self.rowval[dest] = col;
227            self.nzval[dest] = T::zero(); //structural zero
228            self.colptr[col] += 1;
229            diagtoKKT[i] = dest;
230        }
231    }
232
233    // same as fill_diag, but only places zero
234    // entries where the input matrix M has a missing
235    // diagonal entry.  M must be square and TRIU
236    pub(crate) fn fill_missing_diag(&mut self, M: &CscMatrix<T>, initcol: usize) {
237        for i in 0..M.n {
238            // fill out missing diagonal terms only
239            if M.colptr[i] == M.colptr[i+1] ||    // completely empty column
240               M.rowval[M.colptr[i+1]-1] != i
241            {
242                // last element is not on diagonal
243                let dest = self.colptr[i + initcol];
244                self.rowval[dest] = i + initcol;
245                self.nzval[dest] = T::zero(); //structural zero
246                self.colptr[i + initcol] += 1;
247            }
248        }
249    }
250
251    // treats self.colptr as a vector of counts for entries in each column,
252    // and rebuilds a valid colptr from it
253    pub(crate) fn colcount_to_colptr(&mut self) {
254        let mut currentptr = 0;
255        for p in &mut self.colptr {
256            let count = *p;
257            *p = currentptr;
258            currentptr += count;
259        }
260    }
261
262    // converts the colptr vector a vector of entry counts for each column
263    // the final entry is set to zero
264    pub(crate) fn colptr_to_colcount(&mut self) {
265        for i in 0..self.n {
266            self.colptr[i] = self.colptr[i + 1] - self.colptr[i];
267        }
268        self.colptr[self.n] = 0;
269    }
270
271    pub(crate) fn backshift_colptrs(&mut self) {
272        self.colptr.rotate_right(1);
273        self.colptr[0] = 0;
274    }
275
276    pub(crate) fn count_diagonal_entries(&self, shape: MatrixTriangle) -> usize {
277        let mut count = 0;
278
279        match shape {
280            MatrixTriangle::Triu => {
281                for i in 0..self.n {
282                    // compare last entry in each column with
283                    // its row number to identify diagonal entries
284                    if self.colptr[i+1] != self.colptr[i] &&    // nonempty column
285                       self.rowval[self.colptr[i+1]-1] == i
286                    {
287                        // last element is on diagonal
288                        count += 1;
289                    }
290                }
291            }
292
293            MatrixTriangle::Tril => {
294                for i in 0..self.n {
295                    // compare first entry in each column with
296                    // its row number to identify diagonal entries
297                    if self.colptr[i+1] != self.colptr[i] &&    // nonempty column
298                       self.rowval[self.colptr[i]] == i
299                    {
300                        // first element is on diagonal
301                        count += 1;
302                    }
303                }
304            }
305        }
306        count
307    }
308}
309
310#[test]
311fn test_count_diagonal_entries_triu() {
312    let shape = MatrixTriangle::Triu;
313
314    let A = CscMatrix::from(&[
315        [1., 2., 3.], //
316        [0., 0., 4.], //
317        [0., 0., 1.],
318    ]); //
319    assert_eq!(A.count_diagonal_entries(shape), 2);
320
321    let A = CscMatrix::from(&[
322        [1., 0., 3.], //
323        [0., 0., 4.], //
324        [0., 0., 1.],
325    ]); //
326    assert_eq!(A.count_diagonal_entries(shape), 2);
327
328    let A = CscMatrix::from(&[
329        [1., 0., 3.], //
330        [0., 4., 4.], //
331        [0., 0., 1.],
332    ]); //
333    assert_eq!(A.count_diagonal_entries(shape), 3);
334
335    let A = CscMatrix::<f64>::zeros((5, 5)); //
336    assert_eq!(A.count_diagonal_entries(shape), 0);
337}
338
339#[test]
340fn test_count_diagonal_entries_tril() {
341    let shape = MatrixTriangle::Tril;
342
343    let A = CscMatrix::from(&[
344        [1., 0., 0.], //
345        [2., 0., 0.], //
346        [3., 4., 1.],
347    ]); //
348    assert_eq!(A.count_diagonal_entries(shape), 2);
349
350    let A = CscMatrix::from(&[
351        [1., 0., 0.], //
352        [0., 0., 0.], //
353        [3., 4., 1.],
354    ]); //
355    assert_eq!(A.count_diagonal_entries(shape), 2);
356
357    let A = CscMatrix::from(&[
358        [1., 0., 0.], //
359        [0., 4., 0.], //
360        [3., 4., 1.],
361    ]); //
362    assert_eq!(A.count_diagonal_entries(shape), 3);
363
364    let A = CscMatrix::<f64>::zeros((5, 5)); //
365    assert_eq!(A.count_diagonal_entries(shape), 0);
366}