1 /* ========================================================================== */
2 /* === Check/cholmod_write ================================================== */
3 /* ========================================================================== */
4 
5 /* Write a matrix to a file in Matrix Market form.
6  *
7  * A can be sparse or full.
8  *
9  * If present and non-empty, A and Z must have the same dimension.  Z contains
10  * the explicit zero entries in the matrix (which MATLAB drops).  The entries
11  * of Z appear as explicit zeros in the output file.  Z is optional.  If it is
12  * an empty matrix it is ignored.  Z must be sparse or empty, if present.
13  * It is ignored if A is full.
14  *
15  * filename is the name of the output file.  comments is file whose
16  * contents are include after the Matrix Market header and before the first
17  * data line.  Ignored if an empty string or not present.
18  *
19  * Except for the workspace used by cholmod_symmetry (ncol integers) for
20  * the sparse case, these routines use no workspace at all.
21  */
22 
23 #ifndef NCHECK
24 
25 #include "cholmod_config.h"
26 #include "cholmod_internal.h"
27 #include "cholmod_check.h"
28 #include "cholmod_matrixops.h"
29 #include <string.h>
30 #include <ctype.h>
31 
32 #define MMLEN 1024
33 #define MAXLINE MMLEN+6
34 
35 /* ========================================================================== */
36 /* === include_comments ===================================================== */
37 /* ========================================================================== */
38 
39 /* Read in the comments file, if it exists, and copy it to the Matrix Market
40  * file.  A "%" is prepended to each line.  Returns TRUE if successful, FALSE
41  * otherwise.
42  */
43 
include_comments(FILE * f,const char * comments)44 static int include_comments (FILE *f, const char *comments)
45 {
46     FILE *cf = NULL ;
47     char buffer [MAXLINE] ;
48     int ok = TRUE ;
49     if (comments != NULL && comments [0] != '\0')
50     {
51 	cf = fopen (comments, "r") ;
52 	if (cf == NULL)
53 	{
54 	    return (FALSE) ;
55 	}
56 	while (ok && fgets (buffer, MAXLINE, cf) != NULL)
57 	{
58 	    /* ensure the line is not too long */
59 	    buffer [MMLEN-1] = '\0' ;
60 	    buffer [MMLEN-2] = '\n' ;
61 	    ok = ok && (fprintf (f, "%%%s", buffer) > 0) ;
62 	}
63 	fclose (cf) ;
64     }
65     return (ok) ;
66 }
67 
68 
69 /* ========================================================================== */
70 /* === get_value ============================================================ */
71 /* ========================================================================== */
72 
73 /* Get the pth value in the matrix. */
74 
get_value(double * Ax,double * Az,Int p,Int xtype,double * x,double * z)75 static void get_value
76 (
77     double *Ax,	    /* real values, or real/imag. for CHOLMOD_COMPLEX type */
78     double *Az,	    /* imaginary values for CHOLMOD_ZOMPLEX type */
79     Int p,	    /* get the pth entry */
80     Int xtype,	    /* A->xtype: pattern, real, complex, or zomplex */
81     double *x,	    /* the real part */
82     double *z	    /* the imaginary part */
83 )
84 {
85     switch (xtype)
86     {
87 	case CHOLMOD_PATTERN:
88 	    *x = 1 ;
89 	    *z = 0 ;
90 	    break ;
91 
92 	case CHOLMOD_REAL:
93 	    *x = Ax [p] ;
94 	    *z = 0 ;
95 	    break ;
96 
97 	case CHOLMOD_COMPLEX:
98 	    *x = Ax [2*p] ;
99 	    *z = Ax [2*p+1] ;
100 	    break ;
101 
102 	case CHOLMOD_ZOMPLEX:
103 	    *x = Ax [p] ;
104 	    *z = Az [p] ;
105 	    break ;
106     }
107 }
108 
109 
110 /* ========================================================================== */
111 /* === print_value ========================================================== */
112 /* ========================================================================== */
113 
114 /* Print a numeric value to the file, using the shortest format that ensures
115  * the value is written precisely.  Returns TRUE if successful, FALSE otherwise.
116  */
117 
print_value(FILE * f,double x,Int is_integer)118 static int print_value
119 (
120     FILE *f,	    /* file to print to */
121     double x,	    /* value to print */
122     Int is_integer  /* TRUE if printing as an integer */
123 )
124 {
125     double y ;
126     char s [MAXLINE], *p ;
127     Int i, dest = 0, src = 0 ;
128     int width, ok ;
129 
130     if (is_integer)
131     {
132 	i = (Int) x ;
133 	ok = (fprintf (f, ID, i) > 0) ;
134 	return (ok) ;
135     }
136 
137     /* ---------------------------------------------------------------------- */
138     /* handle Inf and NaN */
139     /* ---------------------------------------------------------------------- */
140 
141     /* change -inf to -HUGE_DOUBLE, and change +inf and nan to +HUGE_DOUBLE */
142     if (CHOLMOD_IS_NAN (x) || x >= HUGE_DOUBLE)
143     {
144 	x = HUGE_DOUBLE ;
145     }
146     else if (x <= -HUGE_DOUBLE)
147     {
148 	x = -HUGE_DOUBLE ;
149     }
150 
151     /* ---------------------------------------------------------------------- */
152     /* find the smallest acceptable precision */
153     /* ---------------------------------------------------------------------- */
154 
155     for (width = 6 ; width < 20 ; width++)
156     {
157 	sprintf (s, "%.*g", width, x) ;
158 	sscanf (s, "%lg", &y) ;
159 	if (x == y) break ;
160     }
161 
162     /* ---------------------------------------------------------------------- */
163     /* shorten the string */
164     /* ---------------------------------------------------------------------- */
165 
166     /* change "e+0" to "e", change "e+" to "e", and change "e-0" to "e-" */
167     for (i = 0 ; i < MAXLINE && s [i] != '\0' ; i++)
168     {
169 	if (s [i] == 'e')
170 	{
171 	    if (s [i+1] == '+')
172 	    {
173 		dest = i+1 ;
174 		if (s [i+2] == '0')
175 		{
176 		    /* delete characters s[i+1] and s[i+2] */
177 		    src = i+3 ;
178 		}
179 		else
180 		{
181 		    /* delete characters s[i+1] */
182 		    src = i+2 ;
183 		}
184 	    }
185 	    else if (s [i+1] == '-')
186 	    {
187 		dest = i+2 ;
188 		if (s [i+2] == '0')
189 		{
190 		    /* delete character s[i+2] */
191 		    src = i+3 ;
192 		}
193 		else
194 		{
195 		    /* no change */
196 		    break ;
197 		}
198 	    }
199 	    while (s [src] != '\0')
200 	    {
201 		s [dest++] = s [src++] ;
202 	    }
203 	    s [dest] = '\0' ;
204 	    break ;
205 	}
206     }
207 
208     /* delete the leading "0" if present and not necessary */
209     p = s ;
210     s [MAXLINE-1] = '\0' ;
211     i = strlen (s) ;
212     if (i > 2 && s [0] == '0' && s [1] == '.')
213     {
214 	/* change "0.x" to ".x" */
215 	p = s + 1 ;
216     }
217     else if (i > 3 && s [0] == '-' && s [1] == '0' && s [2] == '.')
218     {
219 	/* change "-0.x" to "-.x" */
220 	s [1] = '-' ;
221 	p = s + 1 ;
222     }
223 
224 #if 0
225     /* double-check */
226     i = sscanf (p, "%lg", &z) ;
227     if (i != 1 || y != z)
228     {
229 	/* oops! something went wrong in the "e+0" edit, above. */
230 	/* this "cannot" happen */
231 	sprintf (s, "%.*g", width, x) ;
232 	p = s ;
233     }
234 #endif
235 
236     /* ---------------------------------------------------------------------- */
237     /* print the value to the file */
238     /* ---------------------------------------------------------------------- */
239 
240     ok = (fprintf (f, "%s", p) > 0) ;
241     return (ok) ;
242 }
243 
244 
245 /* ========================================================================== */
246 /* === print_triplet ======================================================== */
247 /* ========================================================================== */
248 
249 /* Print a triplet, converting it to one-based.  Returns TRUE if successful,
250  * FALSE otherwise.
251  */
252 
print_triplet(FILE * f,Int is_binary,Int is_complex,Int is_integer,Int i,Int j,double x,double z)253 static int print_triplet
254 (
255     FILE *f,		/* file to print to */
256     Int is_binary,	/* TRUE if file is "pattern" */
257     Int is_complex,	/* TRUE if file is "complex" */
258     Int is_integer,	/* TRUE if file is "integer" */
259     Int i,		/* row index (zero-based) */
260     Int j,		/* column index (zero-based) */
261     double x,		/* real part */
262     double z		/* imaginary part */
263 )
264 {
265     int ok ;
266     ok = (fprintf (f, ID " " ID, 1+i, 1+j) > 0) ;
267     if (!is_binary)
268     {
269 	fprintf (f, " ") ;
270 	ok = ok && print_value (f, x, is_integer) ;
271 	if (is_complex)
272 	{
273 	    fprintf (f, " ") ;
274 	    ok = ok && print_value (f, z, is_integer) ;
275 	}
276     }
277     ok = ok && (fprintf (f, "\n") > 0) ;
278     return (ok) ;
279 }
280 
281 
282 /* ========================================================================== */
283 /* === ntriplets ============================================================ */
284 /* ========================================================================== */
285 
286 /* Compute the number of triplets that will be printed to the file
287  * from the matrix A. */
288 
ntriplets(cholmod_sparse * A,Int is_sym)289 static Int ntriplets
290 (
291     cholmod_sparse *A,	    /* matrix that will be printed */
292     Int is_sym		    /* TRUE if the file is symmetric (lower part only)*/
293 )
294 {
295     Int *Ap, *Ai, *Anz, packed, i, j, p, pend, ncol, stype, nz = 0 ;
296     if (A == NULL)
297     {
298 	/* the Z matrix is NULL */
299 	return (0) ;
300     }
301     stype = A->stype ;
302     Ap = A->p ;
303     Ai = A->i ;
304     Anz = A->nz ;
305     packed = A->packed ;
306     ncol = A->ncol ;
307     for (j = 0 ; j < ncol ; j++)
308     {
309 	p = Ap [j] ;
310 	pend = (packed) ? Ap [j+1] : p + Anz [j] ;
311 	for ( ; p < pend ; p++)
312 	{
313 	    i = Ai [p] ;
314 	    if ((stype < 0 && i >= j) || (stype == 0 && (i >= j || !is_sym)))
315 	    {
316 		/* CHOLMOD matrix is symmetric-lower (and so is the file);
317 		 * or CHOLMOD matrix is unsymmetric and either A(i,j) is in
318 		 * the lower part or the file is unsymmetric. */
319 		nz++ ;
320 	    }
321 	    else if (stype > 0 && i <= j)
322 	    {
323 		/* CHOLMOD matrix is symmetric-upper, but the file is
324 		 * symmetric-lower.  Need to transpose the entry. */
325 		nz++ ;
326 	    }
327 	}
328     }
329     return (nz) ;
330 }
331 
332 
333 /* ========================================================================== */
334 /* === cholmod_write_sparse ================================================= */
335 /* ========================================================================== */
336 
337 /* Write a sparse matrix to a file in Matrix Market format.   Optionally include
338  * comments, and print explicit zero entries given by the pattern of the Z
339  * matrix.  If not NULL, the Z matrix must have the same dimensions and stype
340  * as A.
341  *
342  * Returns the symmetry in which the matrix was printed (1 to 7, see the
343  * CHOLMOD_MM_* codes in CHOLMOD/Include/cholmod_core.h), or -1 on failure.
344  *
345  * If A and Z are sorted on input, and either unsymmetric (stype = 0) or
346  * symmetric-lower (stype < 0), and if A and Z do not overlap, then the triplets
347  * are sorted, first by column and then by row index within each column, with
348  * no duplicate entries.  If all the above holds except stype > 0, then the
349  * triplets are sorted by row first and then column.
350  */
351 
CHOLMOD(write_sparse)352 int CHOLMOD(write_sparse)
353 (
354     /* ---- input ---- */
355     FILE *f,		    /* file to write to, must already be open */
356     cholmod_sparse *A,	    /* matrix to print */
357     cholmod_sparse *Z,	    /* optional matrix with pattern of explicit zeros */
358     const char *comments,    /* optional filename of comments to include */
359     /* --------------- */
360     cholmod_common *Common
361 )
362 {
363     double x = 0, z = 0 ;
364     double *Ax, *Az ;
365     Int *Ap, *Ai, *Anz, *Zp, *Zi, *Znz ;
366     Int nrow, ncol, is_complex, symmetry, i, j, q, iz, p, nz, is_binary, stype,
367 	is_integer, asym, is_sym, xtype, apacked, zpacked, pend, qend, zsym ;
368     int ok ;
369 
370     /* ---------------------------------------------------------------------- */
371     /* check inputs */
372     /* ---------------------------------------------------------------------- */
373 
374     RETURN_IF_NULL_COMMON (EMPTY) ;
375     RETURN_IF_NULL (f, EMPTY) ;
376     RETURN_IF_NULL (A, EMPTY) ;
377     RETURN_IF_XTYPE_INVALID (A, CHOLMOD_PATTERN, CHOLMOD_ZOMPLEX, EMPTY) ;
378     if (Z != NULL && (Z->nrow == 0 || Z->ncol == 0))
379     {
380 	/* Z is non-NULL but empty, so treat it as a NULL matrix */
381 	Z = NULL ;
382     }
383     if (Z != NULL)
384     {
385 	RETURN_IF_XTYPE_INVALID (Z, CHOLMOD_PATTERN, CHOLMOD_ZOMPLEX, EMPTY) ;
386 	if (Z->nrow != A->nrow || Z->ncol != A->ncol || Z->stype != A->stype)
387 	{
388 	    ERROR (CHOLMOD_INVALID, "dimension or type of A and Z mismatch") ;
389 	    return (EMPTY) ;
390 	}
391     }
392     Common->status = CHOLMOD_OK ;
393 
394     /* ---------------------------------------------------------------------- */
395     /* get the A matrix */
396     /* ---------------------------------------------------------------------- */
397 
398     Ap = A->p ;
399     Ai = A->i ;
400     Ax = A->x ;
401     Az = A->z ;
402     Anz = A->nz ;
403     nrow = A->nrow ;
404     ncol = A->ncol ;
405     xtype = A->xtype ;
406     apacked = A->packed ;
407 
408     if (xtype == CHOLMOD_PATTERN)
409     {
410 	/* a CHOLMOD pattern matrix is printed as "pattern" in the file */
411 	is_binary = TRUE ;
412 	is_integer = FALSE ;
413 	is_complex = FALSE ;
414     }
415     else if (xtype == CHOLMOD_REAL)
416     {
417 	/* determine if a real matrix is in fact binary or integer */
418 	is_binary = TRUE ;
419 	is_integer = TRUE ;
420 	is_complex = FALSE ;
421 	for (j = 0 ; (is_binary || is_integer) && j < ncol ; j++)
422 	{
423 	    p = Ap [j] ;
424 	    pend = (apacked) ? Ap [j+1] : p + Anz [j] ;
425 	    for ( ; (is_binary || is_integer) && p < pend ; p++)
426 	    {
427 		x = Ax [p] ;
428 		if (x != 1)
429 		{
430 		    is_binary = FALSE ;
431 		}
432 		/* convert to Int and then back to double */
433 		i = (Int) x ;
434 		z = (double) i ;
435 		if (z != x)
436 		{
437 		    is_integer = FALSE ;
438 		}
439 	    }
440 	}
441     }
442     else
443     {
444 	/* a CHOLMOD complex matrix is printed as "complex" in the file */
445 	is_binary = FALSE ;
446 	is_integer = FALSE ;
447 	is_complex = TRUE ;
448     }
449 
450     /* ---------------------------------------------------------------------- */
451     /* get the Z matrix (only consider the pattern) */
452     /* ---------------------------------------------------------------------- */
453 
454     Zp = NULL ;
455     Zi = NULL ;
456     Znz = NULL ;
457     zpacked = TRUE ;
458     if (Z != NULL)
459     {
460 	Zp = Z->p ;
461 	Zi = Z->i ;
462 	Znz = Z->nz ;
463 	zpacked = Z->packed ;
464     }
465 
466     /* ---------------------------------------------------------------------- */
467     /* determine the symmetry of A and Z */
468     /* ---------------------------------------------------------------------- */
469 
470     stype = A->stype ;
471     if (A->nrow != A->ncol)
472     {
473 	asym = CHOLMOD_MM_RECTANGULAR ;
474     }
475     else if (stype != 0)
476     {
477 	/* CHOLMOD's A and Z matrices have a symmetric (and matching) stype.
478 	 * Note that the diagonal is not checked. */
479 	asym = is_complex ? CHOLMOD_MM_HERMITIAN : CHOLMOD_MM_SYMMETRIC ;
480     }
481     else if (!A->sorted)
482     {
483 	/* A is in unsymmetric storage, but unsorted */
484 	asym = CHOLMOD_MM_UNSYMMETRIC ;
485     }
486     else
487     {
488 	/* CHOLMOD's stype is zero (stored in unsymmetric form) */
489 	asym = EMPTY ;
490 	zsym = EMPTY ;
491 
492 #ifndef NMATRIXOPS
493 	/* determine if the matrices are in fact symmetric or Hermitian */
494 	asym = CHOLMOD(symmetry) (A, 1, NULL, NULL, NULL, NULL, Common) ;
495 	zsym = (Z == NULL) ? 999 :
496 	       CHOLMOD(symmetry) (Z, 1, NULL, NULL, NULL, NULL, Common) ;
497 #endif
498 
499 	if (asym == EMPTY || zsym <= CHOLMOD_MM_UNSYMMETRIC)
500 	{
501 	    /* not computed, out of memory, or Z is unsymmetric */
502 	    asym = CHOLMOD_MM_UNSYMMETRIC ;
503 	}
504     }
505 
506     /* ---------------------------------------------------------------------- */
507     /* write the Matrix Market header */
508     /* ---------------------------------------------------------------------- */
509 
510     ok = fprintf (f, "%%%%MatrixMarket matrix coordinate") > 0 ;
511 
512     if (is_complex)
513     {
514 	ok = ok && (fprintf (f, " complex") > 0) ;
515     }
516     else if (is_binary)
517     {
518 	ok = ok && (fprintf (f, " pattern") > 0) ;
519     }
520     else if (is_integer)
521     {
522 	ok = ok && (fprintf (f, " integer") > 0) ;
523     }
524     else
525     {
526 	ok = ok && (fprintf (f, " real") > 0) ;
527     }
528 
529     is_sym = FALSE ;
530 
531     switch (asym)
532     {
533 	case CHOLMOD_MM_RECTANGULAR:
534 	case CHOLMOD_MM_UNSYMMETRIC:
535 	    /* A is rectangular or unsymmetric */
536 	    ok = ok && (fprintf (f, " general\n") > 0) ;
537 	    is_sym = FALSE ;
538 	    symmetry = CHOLMOD_MM_UNSYMMETRIC ;
539 	    break ;
540 
541 	case CHOLMOD_MM_SYMMETRIC:
542 	case CHOLMOD_MM_SYMMETRIC_POSDIAG:
543 	    /* A is symmetric */
544 	    ok = ok && (fprintf (f, " symmetric\n") > 0) ;
545 	    is_sym = TRUE ;
546 	    symmetry = CHOLMOD_MM_SYMMETRIC ;
547 	    break ;
548 
549 	case CHOLMOD_MM_HERMITIAN:
550 	case CHOLMOD_MM_HERMITIAN_POSDIAG:
551 	    /* A is Hermitian */
552 	    ok = ok && (fprintf (f, " Hermitian\n") > 0) ;
553 	    is_sym = TRUE ;
554 	    symmetry = CHOLMOD_MM_HERMITIAN ;
555 	    break ;
556 
557 	case CHOLMOD_MM_SKEW_SYMMETRIC:
558 	    /* A is skew symmetric */
559 	    ok = ok && (fprintf (f, " skew-symmetric\n") > 0) ;
560 	    is_sym = TRUE ;
561 	    symmetry = CHOLMOD_MM_SKEW_SYMMETRIC ;
562 	    break ;
563     }
564 
565     /* ---------------------------------------------------------------------- */
566     /* include the comments if present */
567     /* ---------------------------------------------------------------------- */
568 
569     ok = ok && include_comments (f, comments) ;
570 
571     /* ---------------------------------------------------------------------- */
572     /* write a sparse matrix (A and Z) */
573     /* ---------------------------------------------------------------------- */
574 
575     nz = ntriplets (A, is_sym) + ntriplets (Z, is_sym) ;
576 
577     /* write the first data line, with nrow, ncol, and # of triplets */
578     ok = ok && (fprintf (f, ID " " ID " " ID "\n", nrow, ncol, nz) > 0) ;
579 
580     for (j = 0 ; ok && j < ncol ; j++)
581     {
582 	/* merge column of A and Z */
583 	p = Ap [j] ;
584 	pend = (apacked) ? Ap [j+1] : p + Anz [j] ;
585 	q = (Z == NULL) ? 0 : Zp [j] ;
586 	qend = (Z == NULL) ? 0 : ((zpacked) ? Zp [j+1] : q + Znz [j]) ;
587 	while (ok)
588 	{
589 	    /* get the next row index from A and Z */
590 	    i  = (p < pend) ? Ai [p] : (nrow+1) ;
591 	    iz = (q < qend) ? Zi [q] : (nrow+2) ;
592 	    if (i <= iz)
593 	    {
594 		/* get A(i,j), or quit if both A and Z are exhausted */
595 		if (i == nrow+1) break ;
596 		get_value (Ax, Az, p, xtype, &x, &z) ;
597 		p++ ;
598 	    }
599 	    else
600 	    {
601 		/* get Z(i,j) */
602 		i = iz ;
603 		x = 0 ;
604 		z = 0 ;
605 		q++ ;
606 	    }
607 	    if ((stype < 0 && i >= j) || (stype == 0 && (i >= j || !is_sym)))
608 	    {
609 		/* CHOLMOD matrix is symmetric-lower (and so is the file);
610 		 * or CHOLMOD matrix is unsymmetric and either A(i,j) is in
611 		 * the lower part or the file is unsymmetric. */
612 		ok = ok && print_triplet (f, is_binary, is_complex, is_integer,
613 		    i,j, x,z) ;
614 	    }
615 	    else if (stype > 0 && i <= j)
616 	    {
617 		/* CHOLMOD matrix is symmetric-upper, but the file is
618 		 * symmetric-lower.  Need to transpose the entry.   If the
619 		 * matrix is real, the complex part is ignored.  If the matrix
620 		 * is complex, it Hermitian.
621 		 */
622 		ASSERT (IMPLIES (is_complex, asym == CHOLMOD_MM_HERMITIAN)) ;
623 		if (z != 0)
624 		{
625 		    z = -z ;
626 		}
627 		ok = ok && print_triplet (f, is_binary, is_complex, is_integer,
628 		    j,i, x,z) ;
629 	    }
630 	}
631     }
632 
633     if (!ok)
634     {
635 	ERROR (CHOLMOD_INVALID, "error reading/writing file") ;
636 	return (EMPTY) ;
637     }
638 
639     return (asym) ;
640 }
641 
642 
643 /* ========================================================================== */
644 /* === cholmod_write_dense ================================================== */
645 /* ========================================================================== */
646 
647 /* Write a dense matrix to a file in Matrix Market format.   Optionally include
648  * comments.  Returns > 0 if successful, -1 otherwise (1 if rectangular, 2 if
649  * square).  Future versions may return 1 to 7 on success (a CHOLMOD_MM_* code,
650  * just as cholmod_write_sparse does).
651  *
652  * A dense matrix is written in "general" format; symmetric formats in the
653  * Matrix Market standard are not exploited.
654  */
655 
CHOLMOD(write_dense)656 int CHOLMOD(write_dense)
657 (
658     /* ---- input ---- */
659     FILE *f,		    /* file to write to, must already be open */
660     cholmod_dense *X,	    /* matrix to print */
661     const char *comments,    /* optional filename of comments to include */
662     /* --------------- */
663     cholmod_common *Common
664 )
665 {
666     double x = 0, z = 0 ;
667     double *Xx, *Xz ;
668     Int nrow, ncol, is_complex, i, j, xtype, p ;
669     int ok ;
670 
671     /* ---------------------------------------------------------------------- */
672     /* check inputs */
673     /* ---------------------------------------------------------------------- */
674 
675     RETURN_IF_NULL_COMMON (EMPTY) ;
676     RETURN_IF_NULL (f, EMPTY) ;
677     RETURN_IF_NULL (X, EMPTY) ;
678     RETURN_IF_XTYPE_INVALID (X, CHOLMOD_REAL, CHOLMOD_ZOMPLEX, EMPTY) ;
679     Common->status = CHOLMOD_OK ;
680 
681     /* ---------------------------------------------------------------------- */
682     /* get the X matrix */
683     /* ---------------------------------------------------------------------- */
684 
685     Xx = X->x ;
686     Xz = X->z ;
687     nrow = X->nrow ;
688     ncol = X->ncol ;
689     xtype = X->xtype ;
690     is_complex = (xtype == CHOLMOD_COMPLEX) || (xtype == CHOLMOD_ZOMPLEX) ;
691 
692     /* ---------------------------------------------------------------------- */
693     /* write the Matrix Market header */
694     /* ---------------------------------------------------------------------- */
695 
696     ok = (fprintf (f, "%%%%MatrixMarket matrix array") > 0) ;
697     if (is_complex)
698     {
699 	ok = ok && (fprintf (f, " complex general\n") > 0) ;
700     }
701     else
702     {
703 	ok = ok && (fprintf (f, " real general\n") > 0) ;
704     }
705 
706     /* ---------------------------------------------------------------------- */
707     /* include the comments if present */
708     /* ---------------------------------------------------------------------- */
709 
710     ok = ok && include_comments (f, comments) ;
711 
712     /* ---------------------------------------------------------------------- */
713     /* write a dense matrix */
714     /* ---------------------------------------------------------------------- */
715 
716     /* write the first data line, with nrow and ncol */
717     ok = ok && (fprintf (f, ID " " ID "\n", nrow, ncol) > 0) ;
718 
719     Xx = X->x ;
720     Xz = X->z ;
721     for (j = 0 ; ok && j < ncol ; j++)
722     {
723 	for (i = 0 ; ok && i < nrow ; i++)
724 	{
725 	    p = i + j*nrow ;
726 	    get_value (Xx, Xz, p, xtype, &x, &z) ;
727 	    ok = ok && print_value (f, x, FALSE) ;
728 	    if (is_complex)
729 	    {
730 		ok = ok && (fprintf (f, " ") > 0) ;
731 		ok = ok && print_value (f, z, FALSE) ;
732 	    }
733 	    ok = ok && (fprintf (f, "\n") > 0) ;
734 	}
735     }
736 
737     if (!ok)
738     {
739 	ERROR (CHOLMOD_INVALID, "error reading/writing file") ;
740 	return (EMPTY) ;
741     }
742 
743     return ((nrow == ncol) ? CHOLMOD_MM_UNSYMMETRIC : CHOLMOD_MM_RECTANGULAR) ;
744 }
745 #endif
746