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}