colamd/
symamd.rs

1use crate::colamd;
2use crate::colamd::recommended;
3use crate::internal::*;
4use crate::stats::*;
5
6/// Computes an ordering `P` of a symmetric sparse matrix `A` such that
7/// the Cholesky factorization `PAP' = LL'` remains sparse.
8///
9/// It is based on a column ordering of a matrix `M` constructed so that
10/// the nonzero pattern of `M'M` is the same as `A`. The matrix `A` is
11/// assumed to be symmetric; only the strictly lower triangular part is
12/// accessed.
13///
14/// The row indices of the entries in column `c` of the matrix are
15/// held in `a_i[(p[c])...(p[c+1]-1)]`. The row indices in a
16/// given column `c` need not be in ascending order, and duplicate
17/// row indices may be present. However, `symamd` will run faster
18/// if the columns are in sorted order with no duplicate entries.
19///
20/// The matrix is 0-based. That is, rows are in the range 0 to
21/// `n-1`, and columns are in the range 0 to `n-1`. `symamd`
22/// returns `false` if any row index is out of range.
23///
24/// `p` is an integer array of size `n+1`. On input, it holds the
25/// "pointers" for the column form of the matrix `A`. Column `c` of
26/// the matrix `A` is held in `a_i[(p[c])...(p[c+1]-1)]`. The first
27/// entry, `p[0]`, must be zero, and `p[c] <= p[c+1]` must hold
28/// for all `c` in the range 0 to `n-1`. The value `p[n]` is
29/// thus the total number of entries in the pattern of the matrix `A`.
30/// `symamd` returns `false` if these conditions are not met.
31///
32/// On output, if `symamd` returns `true`, the array `perm` holds the
33/// permutation `P`, where `perm[0]` is the first index in the new
34/// ordering, and `perm[n-1]` is the last. That is, `perm[k] = j`
35/// means that row and column `j` of `A` is the `k`th column in `PAP'`,
36/// where `k` is in the range 0 to `n-1` (`perm[0] = j` means
37/// that row and column `j` of `A` are the first row and column in
38/// `PAP'`). The array is used as a workspace during the ordering,
39/// which is why it must be of length `n+1`, not just `n`.
40///
41/// ```txt
42/// stats[0]: number of dense or empty row and columns ignored
43///    (and ordered last in the output permutation
44///    perm). Note that a row/column can become
45///    "empty" if it contains only "dense" and/or
46///    "empty" columns/rows.
47/// stats[1]: (same as `stats[0]`)
48/// stats[2]: number of garbage collections performed.
49/// stats[3]: status code < 0 is an error code.
50///      > 1 is a warning or notice.
51///
52///     0: OK. Each column of the input matrix contained
53///       row indices in increasing order, with no
54///       duplicates.
55///
56///     1: OK, but columns of input matrix were jumbled
57///       (unsorted columns or duplicate entries). Symamd
58///       had to do some extra work to sort the matrix
59///       first and remove duplicate entries, but it
60///       still was able to return a valid permutation
61///       (return value of symamd was true).
62///
63///         stats[4]: highest numbered column that
64///           is unsorted or has duplicate entries.
65///         stats[5]: last seen duplicate or unsorted row index.
66///         stats[6]: number of duplicate or unsorted row indices.
67///     -1: A is a null pointer
68///     -2: p is a null pointer
69///     -3: (unused, see colamd.go)
70///     -4:  n is negative
71///         stats[4]: n
72///     -5: Number of nonzeros in matrix is negative
73///         stats[4]: # of nonzeros (p [n]).
74///     -6: p[0] is nonzero
75///         stats[4]: p[0]
76///     -7: (unused)
77///     -8: A column has a negative number of entries
78///         stats[4]: column with < 0 entries
79///         stats[5]: number of entries in col
80///     -9: A row index is out of bounds
81///         stats[4]: column with bad row index
82///         stats[5]: bad row index
83///         stats[6]: n_row, # of rows of matrix
84///     -10: Out of memory (unable to allocate temporary
85///       workspace for M or count arrays using the
86///       "allocate" routine passed into symamd).
87/// ```
88pub fn symamd(
89    n: Int,
90    a_i: &[Int],
91    p: &[Int],
92    perm: &mut [Int],
93    knobs: Option<[f64; KNOBS]>,
94    stats: &mut [Int; STATS],
95) -> bool {
96    let mut cknobs = [0.0; KNOBS]; // Knobs for colamd.
97
98    for i in 0..STATS {
99        stats[i] = 0;
100    }
101    stats[STATUS] = OK;
102    stats[INFO1] = -1;
103    stats[INFO2] = -1;
104
105    if n < 0 {
106        // n must be >= 0
107        stats[STATUS] = ERROR_NCOL_NEGATIVE;
108        stats[INFO1] = n;
109        debug0!("symamd: n negative {}", n);
110        return false;
111    }
112
113    let nnz = p[n as usize];
114    if nnz < 0 {
115        // nnz must be >= 0
116        stats[STATUS] = ERROR_NNZ_NEGATIVE;
117        stats[INFO1] = nnz;
118        debug0!("symamd: number of entries negative {}", nnz);
119        return false;
120    }
121
122    if p[0] != 0 {
123        stats[STATUS] = ERROR_P0_NONZERO;
124        stats[INFO1] = p[0];
125        debug0!("symamd: p[0] not zero {}", p[0]);
126        return false;
127    }
128
129    // If no knobs, set default knobs.
130
131    let knobs = if knobs.is_none() {
132        default_knobs()
133    } else {
134        knobs.unwrap()
135    };
136
137    // Allocate count and mark.
138
139    let mut count = vec![0; (n + 1) as usize]; // Length of each column of M, and col pointer.
140    let mut mark = vec![0; (n + 1) as usize]; // Mark array for finding duplicate entries.
141
142    // Compute column counts of M, check if A is valid.
143
144    stats[INFO3] = 0; // Number of duplicate or unsorted row indices.
145
146    for i in 0..n as usize {
147        mark[i] = -1;
148    }
149
150    for j in 0..n {
151        let mut last_row = -1; // Last row seen in the current column.
152
153        let length = p[(j + 1) as usize] - p[j as usize]; // Number of nonzeros in a column.
154        if length < 0 {
155            // Column pointers must be non-decreasing.
156            stats[STATUS] = ERROR_COL_LENGTH_NEGATIVE;
157            stats[INFO1] = j;
158            stats[INFO2] = length;
159            // count = None;
160            // mark = None;
161            debug0!("symamd: col {} negative length {}", j, length);
162            return false;
163        }
164
165        for pp in p[j as usize]..p[(j + 1) as usize] {
166            let i = a_i[pp as usize];
167            if i < 0 || i >= n {
168                // Row index i, in column j, is out of bounds.
169                stats[STATUS] = ERROR_ROW_INDEX_OUT_OF_BOUNDS;
170                stats[INFO1] = j;
171                stats[INFO2] = i;
172                stats[INFO3] = n;
173                // count = None;
174                // mark = None;
175                debug0!("symamd: row {} col {} out of bounds", i, j);
176                return false;
177            }
178
179            if i <= last_row || mark[i as usize] == j {
180                // Row index is unsorted or repeated (or both), thus col
181                // is jumbled. This is a notice, not an error condition.
182                stats[STATUS] = OK_BUT_JUMBLED;
183                stats[INFO1] = j;
184                stats[INFO2] = i;
185                stats[INFO3] += 1;
186                debug1!("symamd: row {} col {} unsorted/duplicate", i, j);
187            }
188
189            if i > j && mark[i as usize] != j {
190                // Row k of M will contain column indices i and j.
191                count[i as usize] += 1;
192                count[j as usize] += 1;
193            }
194
195            // Mark the row as having been seen in this column.
196            mark[i as usize] = j;
197
198            last_row = i;
199        }
200    }
201
202    // Compute column pointers of M.
203
204    // Use output permutation, perm, for column pointers of M.
205    perm[0] = 0;
206    for j in 1..=n as usize {
207        perm[j] = perm[j - 1] + count[j - 1];
208    }
209    for j in 0..n as usize {
210        count[j] = perm[j]
211    }
212
213    // Construct M.
214
215    let mnz = perm[n as usize];
216    let nrow = mnz / 2;
217    let m_len = recommended(mnz, nrow, n);
218    let mut m_mat = vec![0; m_len as usize];
219    debug0!(
220        "symamd: M is {}-by-{} with {} entries, Mlen = {}",
221        nrow,
222        n,
223        mnz,
224        m_len
225    );
226
227    let mut k = 0;
228
229    if stats[STATUS] == OK {
230        // Matrix is OK.
231        for j in 0..n as usize {
232            assert_debug!(p[j + 1] - p[j] >= 0);
233
234            for pp in p[j]..p[j + 1] {
235                let i = a_i[pp as usize] as usize;
236                assert_debug!(/*i >= 0 &&*/ i < n as usize);
237
238                if i > j {
239                    // Row k of M contains column indices i and j.
240                    m_mat[count[i] as usize] = k;
241                    count[i] += 1;
242                    m_mat[count[j] as usize] = k;
243                    count[j] += 1;
244                    k += 1;
245                }
246            }
247        }
248    } else {
249        // Matrix is jumbled. Do not add duplicates to M. Unsorted cols OK.
250        debug0!("symamd: Duplicates in A.");
251
252        for i in 0..n {
253            mark[i as usize] = -1;
254        }
255        for j in 0..n as usize {
256            assert_debug!(p[j + 1] - p[j] >= 0);
257
258            for pp in p[j]..p[j + 1] {
259                let i = a_i[pp as usize] as usize;
260                assert_debug!(/*i >= 0 &&*/ i < n as usize);
261
262                if i > j && mark[i] as usize != j {
263                    // Row k of M contains column indices i and j.
264                    m_mat[count[i] as usize] = k;
265                    count[i] += 1;
266                    m_mat[count[j] as usize] = k;
267                    count[j] += 1;
268                    k += 1;
269                    mark[i] = j as Int;
270                }
271            }
272        }
273    }
274
275    assert_debug!(k == nrow);
276
277    // Adjust the knobs for M.
278
279    for i in 0..KNOBS {
280        cknobs[i] = knobs[i];
281    }
282
283    // There are no dense rows in M.
284    cknobs[DENSE_ROW] = -1.0;
285    cknobs[DENSE_COL] = knobs[DENSE_ROW];
286
287    debug0!("symamd: dense col knob for M: {}\n", cknobs[DENSE_COL]);
288
289    // Order the columns of M.
290
291    colamd(nrow, n, m_len, &mut m_mat, perm, Some(cknobs), stats);
292
293    // Note that the output permutation is now in perm.
294
295    // Get the statistics for symamd from colamd.
296
297    // A dense column in colamd means a dense row and col in symamd.
298    stats[DENSE_ROW] = stats[DENSE_COL];
299
300    debug0!("symamd: done.");
301
302    true
303}