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