1 /* ========================================================================== */
2 /* === MatrixOps/cholmod_symmetry =========================================== */
3 /* ========================================================================== */
4
5 /* -----------------------------------------------------------------------------
6 * CHOLMOD/MatrixOps Module. Copyright (C) 2005-2006, Timothy A. Davis
7 * http://www.suitesparse.com
8 * -------------------------------------------------------------------------- */
9
10 /* Determines if a sparse matrix is rectangular, unsymmetric, symmetric,
11 * skew-symmetric, or Hermitian. It does so by looking at its numerical values
12 * of both upper and lower triangular parts of a CHOLMOD "unsymmetric"
13 * matrix, where A->stype == 0. The transpose of A is NOT constructed.
14 *
15 * If not unsymmetric, it also determines if the matrix has a diagonal whose
16 * entries are all real and positive (and thus a candidate for sparse Cholesky
17 * if A->stype is changed to a nonzero value).
18 *
19 * Note that a Matrix Market "general" matrix is either rectangular or
20 * unsymmetric.
21 *
22 * The row indices in the column of each matrix MUST be sorted for this function
23 * to work properly (A->sorted must be TRUE). This routine returns EMPTY if
24 * A->stype is not zero, or if A->sorted is FALSE. The exception to this rule
25 * is if A is rectangular.
26 *
27 * If option == 0, then this routine returns immediately when it finds a
28 * non-positive diagonal entry (or one with nonzero imaginary part). If the
29 * matrix is not a candidate for sparse Cholesky, it returns the value
30 * CHOLMOD_MM_UNSYMMETRIC, even if the matrix might in fact be symmetric or
31 * Hermitian.
32 *
33 * This routine is useful inside the MATLAB backslash, which must look at an
34 * arbitrary matrix (A->stype == 0) and determine if it is a candidate for
35 * sparse Cholesky. In that case, option should be 0.
36 *
37 * This routine is also useful when writing a MATLAB matrix to a file in
38 * Rutherford/Boeing or Matrix Market format. Those formats require a
39 * determination as to the symmetry of the matrix, and thus this routine should
40 * not return upon encountering the first non-positive diagonal. In this case,
41 * option should be 1.
42 *
43 * If option is 2, this function can be used to compute the numerical and
44 * pattern symmetry, where 0 is a completely unsymmetric matrix, and 1 is a
45 * perfectly symmetric matrix. This option is used when computing the following
46 * statistics for the matrices in the UF Sparse Matrix Collection.
47 *
48 * numerical symmetry: number of matched offdiagonal nonzeros over
49 * the total number of offdiagonal entries. A real entry A(i,j), i ~= j,
50 * is matched if A (j,i) == A (i,j), but this is only counted if both
51 * A(j,i) and A(i,j) are nonzero. This does not depend on Z.
52 * (If A is complex, then the above test is modified; A (i,j) is matched
53 * if conj (A (j,i)) == A (i,j)).
54 *
55 * Then numeric symmetry = xmatched / nzoffdiag, or 1 if nzoffdiag = 0.
56 *
57 * pattern symmetry: number of matched offdiagonal entries over the
58 * total number of offdiagonal entries. An entry A(i,j), i ~= j, is
59 * matched if A (j,i) is also an entry.
60 *
61 * Then pattern symmetry = pmatched / nzoffdiag, or 1 if nzoffdiag = 0.
62 *
63 * The symmetry of a matrix with no offdiagonal entries is equal to 1.
64 *
65 * A workspace of size ncol integers is allocated; EMPTY is returned if this
66 * allocation fails.
67 *
68 * Summary of return values:
69 *
70 * EMPTY (-1) out of memory, stype not zero, A not sorted
71 * CHOLMOD_MM_RECTANGULAR 1 A is rectangular
72 * CHOLMOD_MM_UNSYMMETRIC 2 A is unsymmetric
73 * CHOLMOD_MM_SYMMETRIC 3 A is symmetric, but with non-pos. diagonal
74 * CHOLMOD_MM_HERMITIAN 4 A is Hermitian, but with non-pos. diagonal
75 * CHOLMOD_MM_SKEW_SYMMETRIC 5 A is skew symmetric
76 * CHOLMOD_MM_SYMMETRIC_POSDIAG 6 A is symmetric with positive diagonal
77 * CHOLMOD_MM_HERMITIAN_POSDIAG 7 A is Hermitian with positive diagonal
78 *
79 * See also the spsym mexFunction, which is a MATLAB interface for this code.
80 *
81 * If the matrix is a candidate for sparse Cholesky, it will return a result
82 * CHOLMOD_MM_SYMMETRIC_POSDIAG if real, or CHOLMOD_MM_HERMITIAN_POSDIAG if
83 * complex. Otherwise, it will return a value less than this. This is true
84 * regardless of the value of the option parameter.
85 */
86
87 #ifndef NGPL
88 #ifndef NMATRIXOPS
89
90 #include "cholmod_internal.h"
91 #include "cholmod_matrixops.h"
92
93
94 /* ========================================================================== */
95 /* === get_value ============================================================ */
96 /* ========================================================================== */
97
98 /* Get the pth value in the matrix. */
99
get_value(double * Ax,double * Az,Int p,Int xtype,double * x,double * z)100 static void get_value
101 (
102 double *Ax, /* real values, or real/imag. for CHOLMOD_COMPLEX type */
103 double *Az, /* imaginary values for CHOLMOD_ZOMPLEX type */
104 Int p, /* get the pth entry */
105 Int xtype, /* A->xtype: pattern, real, complex, or zomplex */
106 double *x, /* the real part */
107 double *z /* the imaginary part */
108 )
109 {
110 switch (xtype)
111 {
112 case CHOLMOD_PATTERN:
113 *x = 1 ;
114 *z = 0 ;
115 break ;
116
117 case CHOLMOD_REAL:
118 *x = Ax [p] ;
119 *z = 0 ;
120 break ;
121
122 case CHOLMOD_COMPLEX:
123 *x = Ax [2*p] ;
124 *z = Ax [2*p+1] ;
125 break ;
126
127 case CHOLMOD_ZOMPLEX:
128 *x = Ax [p] ;
129 *z = Az [p] ;
130 break ;
131 }
132 }
133
134
135 /* ========================================================================== */
136 /* === cholmod_symmetry ===================================================== */
137 /* ========================================================================== */
138
139 /* Determine the symmetry of a matrix, and check its diagonal.
140 *
141 * option 0: Do not count # of matched pairs. Quick return if the
142 * the matrix has a zero, negative, or imaginary diagonal entry.
143 *
144 * option 1: Do not count # of matched pairs. Do not return quickly if
145 * the matrix has a zero, negative, or imaginary diagonal entry.
146 * The result 1 to 7 is accurately computed:
147 *
148 * EMPTY (-1) out of memory, stype not zero, A not sorted
149 * CHOLMOD_MM_RECTANGULAR 1 A is rectangular
150 * CHOLMOD_MM_UNSYMMETRIC 2 A is unsymmetric
151 * CHOLMOD_MM_SYMMETRIC 3 A is symmetric, with non-pos. diagonal
152 * CHOLMOD_MM_HERMITIAN 4 A is Hermitian, with non-pos. diagonal
153 * CHOLMOD_MM_SKEW_SYMMETRIC 5 A is skew symmetric
154 * CHOLMOD_MM_SYMMETRIC_POSDIAG 6 is symmetric with positive diagonal
155 * CHOLMOD_MM_HERMITIAN_POSDIAG 7 A is Hermitian with positive diagonal
156 *
157 * The routine returns as soon as the above is determined (that is, it
158 * can return as soon as it determines the matrix is unsymmetric).
159 *
160 * option 2: All of the above, but also compute the number of matched off-
161 * diagonal entries (of two types). xmatched is the number of
162 * nonzero entries for which A(i,j) = conj(A(j,i)). pmatched is
163 * the number of entries (i,j) for which A(i,j) and A(j,i) are both in
164 * the pattern of A (the value doesn't matter). nzoffdiag is the total
165 * number of off-diagonal entries in the pattern. nzdiag is the number of
166 * diagonal entries in the pattern.
167 *
168 * With option 0 or 1, or if the matrix is rectangular, xmatched, pmatched,
169 * nzoffdiag, and nzdiag are not computed.
170 *
171 * Note that a matched pair, A(i,j) and A(j,i) for i != j, is counted twice
172 * (once per entry).
173 */
174
CHOLMOD(symmetry)175 int CHOLMOD(symmetry)
176 (
177 /* ---- input ---- */
178 cholmod_sparse *A,
179 int option, /* option 0, 1, or 2 (see above) */
180 /* ---- output --- */ /* outputs ignored if any are NULL */
181 Int *p_xmatched, /* # of matched numerical entries */
182 Int *p_pmatched, /* # of matched entries in pattern */
183 Int *p_nzoffdiag, /* # of off diagonal entries */
184 Int *p_nzdiag, /* # of diagonal entries */
185 /* --------------- */
186 cholmod_common *Common
187 )
188 {
189 double aij_real = 0, aij_imag = 0, aji_real = 0, aji_imag = 0 ;
190 double *Ax, *Az ;
191 Int *Ap, *Ai, *Anz, *munch ;
192 Int packed, nrow, ncol, xtype, is_symmetric, is_skew, is_hermitian, posdiag,
193 j, p, pend, i, piend, result, xmatched, pmatched, nzdiag, i2, found ;
194
195 /* ---------------------------------------------------------------------- */
196 /* check inputs */
197 /* ---------------------------------------------------------------------- */
198
199 RETURN_IF_NULL_COMMON (EMPTY) ;
200 RETURN_IF_NULL (A, EMPTY) ;
201 RETURN_IF_XTYPE_INVALID (A, CHOLMOD_PATTERN, CHOLMOD_ZOMPLEX, EMPTY) ;
202 Common->status = CHOLMOD_OK ;
203 ASSERT (CHOLMOD(dump_sparse) (A, "cholmod_symmetry", Common) >= 0) ;
204
205 if (p_xmatched == NULL || p_pmatched == NULL
206 || p_nzoffdiag == NULL || p_nzdiag == NULL)
207 {
208 /* option 2 is not performed if any output parameter is NULL */
209 option = MAX (option, 1) ;
210 }
211
212 /* ---------------------------------------------------------------------- */
213 /* get inputs */
214 /* ---------------------------------------------------------------------- */
215
216 Ap = A->p ;
217 Ai = A->i ;
218 Ax = A->x ;
219 Az = A->z ;
220 Anz = A->nz ;
221 packed = A->packed ;
222 ncol = A->ncol ;
223 nrow = A->nrow ;
224 xtype = A->xtype ;
225
226 /* ---------------------------------------------------------------------- */
227 /* check if rectangular, unsorted, or stype is not zero */
228 /* ---------------------------------------------------------------------- */
229
230 if (nrow != ncol)
231 {
232 /* matrix is rectangular */
233 return (CHOLMOD_MM_RECTANGULAR) ;
234 }
235
236 if (!(A->sorted) || A->stype != 0)
237 {
238 /* this function cannot determine the type or symmetry */
239 return (EMPTY) ;
240 }
241
242 /* ---------------------------------------------------------------------- */
243 /* allocate workspace */
244 /* ---------------------------------------------------------------------- */
245
246 /* this function requires uninitialized Int workspace of size ncol */
247 CHOLMOD(allocate_work) (0, ncol, 0, Common) ;
248 if (Common->status < CHOLMOD_OK)
249 {
250 /* out of memory */
251 return (EMPTY) ;
252 }
253
254 munch = Common->Iwork ; /* the munch array is size ncol */
255
256 /* ---------------------------------------------------------------------- */
257 /* determine symmetry of a square matrix */
258 /* ---------------------------------------------------------------------- */
259
260 /* a complex or zomplex matrix is Hermitian until proven otherwise */
261 is_hermitian = (xtype >= CHOLMOD_COMPLEX) ;
262
263 /* any matrix is symmetric until proven otherwise */
264 is_symmetric = TRUE ;
265
266 /* a non-pattern matrix is skew-symmetric until proven otherwise */
267 is_skew = (xtype != CHOLMOD_PATTERN) ;
268
269 /* a matrix has positive diagonal entries until proven otherwise */
270 posdiag = TRUE ;
271
272 /* munch pointers start at the top of each column */
273 for (j = 0 ; j < ncol ; j++)
274 {
275 munch [j] = Ap [j] ;
276 }
277
278 xmatched = 0 ;
279 pmatched = 0 ;
280 nzdiag = 0 ;
281
282 for (j = 0 ; j < ncol ; j++) /* examine each column of A */
283 {
284
285 /* ------------------------------------------------------------------ */
286 /* look at the entire munch column j */
287 /* ------------------------------------------------------------------ */
288
289 /* start at the munch point of column j, and go to end of the column */
290 p = munch [j] ;
291 pend = (packed) ? (Ap [j+1]) : (Ap [j] + Anz [j]) ;
292
293 for ( ; p < pend ; p++)
294 {
295 /* get the row index of A(i,j) */
296 i = Ai [p] ;
297
298 if (i < j)
299 {
300
301 /* ---------------------------------------------------------- */
302 /* A(i,j) in triu(A), but matching A(j,i) not in tril(A) */
303 /* ---------------------------------------------------------- */
304
305 /* entry A(i,j) is unmatched; it appears in the upper triangular
306 * part, but not the lower triangular part. The matrix is
307 * unsymmetric. */
308 is_hermitian = FALSE ;
309 is_symmetric = FALSE ;
310 is_skew = FALSE ;
311
312 }
313 else if (i == j)
314 {
315
316 /* ---------------------------------------------------------- */
317 /* the diagonal A(j,j) is present; check its value */
318 /* ---------------------------------------------------------- */
319
320 get_value (Ax, Az, p, xtype, &aij_real, &aij_imag) ;
321 if (aij_real != 0. || aij_imag != 0.)
322 {
323 /* diagonal is nonzero; matrix is not skew-symmetric */
324 nzdiag++ ;
325 is_skew = FALSE ;
326 }
327 if (aij_real <= 0. || aij_imag != 0.)
328 {
329 /* diagonal negative or imaginary; not chol candidate */
330 posdiag = FALSE ;
331 }
332 if (aij_imag != 0.)
333 {
334 /* imaginary part is present; not Hermitian */
335 is_hermitian = FALSE ;
336 }
337
338 }
339 else /* i > j */
340 {
341
342 /* ---------------------------------------------------------- */
343 /* consider column i, up to and including row j */
344 /* ---------------------------------------------------------- */
345
346 /* munch the entry at top of column i up to and incl row j */
347 piend = (packed) ? (Ap [i+1]) : (Ap [i] + Anz [i]) ;
348
349 found = FALSE ;
350
351 for ( ; munch [i] < piend ; munch [i]++)
352 {
353
354 i2 = Ai [munch [i]] ;
355
356 if (i2 < j)
357 {
358
359 /* -------------------------------------------------- */
360 /* A(i2,i) in triu(A) but A(i,i2) not in tril(A) */
361 /* -------------------------------------------------- */
362
363 /* The matrix is unsymmetric. */
364 is_hermitian = FALSE ;
365 is_symmetric = FALSE ;
366 is_skew = FALSE ;
367
368 }
369 else if (i2 == j)
370 {
371
372 /* -------------------------------------------------- */
373 /* both A(i,j) and A(j,i) exist in the matrix */
374 /* -------------------------------------------------- */
375
376 /* this is one more matching entry in the pattern */
377 pmatched += 2 ;
378 found = TRUE ;
379
380 /* get the value of A(i,j) */
381 get_value (Ax, Az, p, xtype, &aij_real, &aij_imag) ;
382
383 /* get the value of A(j,i) */
384 get_value (Ax, Az, munch [i],
385 xtype, &aji_real, &aji_imag) ;
386
387 /* compare A(i,j) with A(j,i) */
388 if (aij_real != aji_real || aij_imag != aji_imag)
389 {
390 /* the matrix cannot be symmetric */
391 is_symmetric = FALSE ;
392 }
393 if (aij_real != -aji_real || aij_imag != aji_imag)
394 {
395 /* the matrix cannot be skew-symmetric */
396 is_skew = FALSE ;
397 }
398 if (aij_real != aji_real || aij_imag != -aji_imag)
399 {
400 /* the matrix cannot be Hermitian */
401 is_hermitian = FALSE ;
402 }
403 else
404 {
405 /* A(i,j) and A(j,i) are numerically matched */
406 xmatched += 2 ;
407 }
408
409 }
410 else /* i2 > j */
411 {
412
413 /* -------------------------------------------------- */
414 /* entry A(i2,i) is not munched; consider it later */
415 /* -------------------------------------------------- */
416
417 break ;
418 }
419 }
420
421 if (!found)
422 {
423 /* A(i,j) in tril(A) but A(j,i) not in triu(A).
424 * The matrix is unsymmetric. */
425 is_hermitian = FALSE ;
426 is_symmetric = FALSE ;
427 is_skew = FALSE ;
428 }
429 }
430
431 if (option < 2 && !(is_symmetric || is_skew || is_hermitian))
432 {
433 /* matrix is unsymmetric; terminate the test */
434 return (CHOLMOD_MM_UNSYMMETRIC) ;
435 }
436 }
437
438 /* ------------------------------------------------------------------ */
439 /* quick return if not Cholesky candidate */
440 /* ------------------------------------------------------------------ */
441
442 if (option < 1 && (!posdiag || nzdiag <= j))
443 {
444 /* Diagonal entry not present, or present but negative or with
445 * nonzero imaginary part. Quick return for option 0. */
446 return (CHOLMOD_MM_UNSYMMETRIC) ;
447 }
448 }
449
450 /* ---------------------------------------------------------------------- */
451 /* return the results */
452 /* ---------------------------------------------------------------------- */
453
454 if (nzdiag < ncol)
455 {
456 /* not all diagonal entries are present */
457 posdiag = FALSE ;
458 }
459
460 if (option >= 2)
461 {
462 *p_xmatched = xmatched ;
463 *p_pmatched = pmatched ;
464 *p_nzoffdiag = CHOLMOD(nnz) (A, Common) - nzdiag ;
465 *p_nzdiag = nzdiag ;
466 }
467
468 result = CHOLMOD_MM_UNSYMMETRIC ;
469 if (is_hermitian)
470 {
471 /* complex Hermitian matrix, with either pos. or non-pos. diagonal */
472 result = posdiag ? CHOLMOD_MM_HERMITIAN_POSDIAG : CHOLMOD_MM_HERMITIAN ;
473 }
474 else if (is_symmetric)
475 {
476 /* real or complex symmetric matrix, with pos. or non-pos. diagonal */
477 result = posdiag ? CHOLMOD_MM_SYMMETRIC_POSDIAG : CHOLMOD_MM_SYMMETRIC ;
478 }
479 else if (is_skew)
480 {
481 /* real or complex skew-symmetric matrix */
482 result = CHOLMOD_MM_SKEW_SYMMETRIC ;
483 }
484 return (result) ;
485 }
486 #endif
487 #endif
488