예제 #1
0
파일: spqr_mx.cpp 프로젝트: GHilmarG/Ua
mxArray *spqr_mx_put_sparse
(
    cholmod_sparse **Ahandle,	// CHOLMOD version of the matrix
    cholmod_common *cc
)
{
    mxArray *Amatlab ;
    cholmod_sparse *A ;
    Long nz, is_complex ;

    A = *Ahandle ;
    is_complex = (A->xtype != CHOLMOD_REAL) ;
    Amatlab = mxCreateSparse (0, 0, 0, is_complex ? mxCOMPLEX: mxREAL) ;
    mxSetM (Amatlab, A->nrow) ;
    mxSetN (Amatlab, A->ncol) ;
    mxSetNzmax (Amatlab, A->nzmax) ;
    mxFree (mxGetJc (Amatlab)) ;
    mxFree (mxGetIr (Amatlab)) ;
    mxFree (mxGetPr (Amatlab)) ;
    mxSetJc (Amatlab, (mwIndex *) A->p) ;
    mxSetIr (Amatlab, (mwIndex *) A->i) ;

    nz = cholmod_l_nnz (A, cc) ;
    put_values (nz, Amatlab, (double *) A->x, is_complex, cc) ;

    A->p = NULL ;
    A->i = NULL ;
    A->x = NULL ;
    A->z = NULL ;
    cholmod_l_free_sparse (Ahandle, cc) ;
    return (Amatlab) ;
}
예제 #2
0
파일: mread.c 프로젝트: sergiud/SuiteSparse
void mexFunction
(
    int	nargout,
    mxArray *pargout [ ],
    int	nargin,
    const mxArray *pargin [ ]
)
{
    void *G ;
    cholmod_dense *X = NULL ;
    cholmod_sparse *A = NULL, *Z = NULL ;
    cholmod_common Common, *cm ;
    Long *Ap = NULL, *Ai ;
    double *Ax, *Az = NULL ;
    char filename [MAXLEN] ;
    Long nz, k, is_complex = FALSE, nrow = 0, ncol = 0, allzero ;
    int mtype ;

    /* ---------------------------------------------------------------------- */
    /* start CHOLMOD and set parameters */
    /* ---------------------------------------------------------------------- */

    cm = &Common ;
    cholmod_l_start (cm) ;
    sputil_config (SPUMONI, cm) ;

    /* ---------------------------------------------------------------------- */
    /* get inputs */
    /* ---------------------------------------------------------------------- */

    if (nargin < 1 || nargin > 2 || nargout > 2)
    {
        mexErrMsgTxt ("usage: [A Z] = mread (filename, prefer_binary)") ;
    }
    if (!mxIsChar (pargin [0]))
    {
        mexErrMsgTxt ("mread requires a filename") ;
    }
    mxGetString (pargin [0], filename, MAXLEN) ;
    sputil_file = fopen (filename, "r") ;
    if (sputil_file == NULL)
    {
        mexErrMsgTxt ("cannot open file") ;
    }
    if (nargin > 1)
    {
        cm->prefer_binary = (mxGetScalar (pargin [1]) != 0) ;
    }

    /* ---------------------------------------------------------------------- */
    /* read the matrix, as either a dense or sparse matrix */
    /* ---------------------------------------------------------------------- */

    G = cholmod_l_read_matrix (sputil_file, 1, &mtype, cm) ;
    fclose (sputil_file) ;
    sputil_file = NULL ;
    if (G == NULL)
    {
        mexErrMsgTxt ("could not read file") ;
    }

    /* get the specific matrix (A or X), and change to ZOMPLEX if needed */
    if (mtype == CHOLMOD_SPARSE)
    {
        A = (cholmod_sparse *) G ;
        nrow = A->nrow ;
        ncol = A->ncol ;
        is_complex = (A->xtype == CHOLMOD_COMPLEX) ;
        Ap = A->p ;
        Ai = A->i ;
        if (is_complex)
        {
            /* if complex, ensure A is ZOMPLEX */
            cholmod_l_sparse_xtype (CHOLMOD_ZOMPLEX, A, cm) ;
        }
        Ax = A->x ;
        Az = A->z ;
    }
    else if (mtype == CHOLMOD_DENSE)
    {
        X = (cholmod_dense *) G ;
        nrow = X->nrow ;
        ncol = X->ncol ;
        is_complex = (X->xtype == CHOLMOD_COMPLEX) ;
        if (is_complex)
        {
            /* if complex, ensure X is ZOMPLEX */
            cholmod_l_dense_xtype (CHOLMOD_ZOMPLEX, X, cm) ;
        }
        Ax = X->x ;
        Az = X->z ;
    }
    else
    {
        mexErrMsgTxt ("invalid file") ;
    }

    /* ---------------------------------------------------------------------- */
    /* if requested, extract the zero entries and place them in Z */
    /* ---------------------------------------------------------------------- */

    if (nargout > 1)
    {
        if (mtype == CHOLMOD_SPARSE)
        {
            /* A is a sparse real/zomplex double matrix */
            Z = sputil_extract_zeros (A, cm) ;
        }
        else
        {
            /* input is full; just return an empty Z matrix */
            Z = cholmod_l_spzeros (nrow, ncol, 0, CHOLMOD_REAL, cm) ;
        }
    }

    /* ---------------------------------------------------------------------- */
    /* prune the zero entries from A and set nzmax(A) to nnz(A) */
    /* ---------------------------------------------------------------------- */

    if (mtype == CHOLMOD_SPARSE)
    {
        sputil_drop_zeros (A) ;
        cholmod_l_reallocate_sparse (cholmod_l_nnz (A, cm), A, cm) ;
    }

    /* ---------------------------------------------------------------------- */
    /* change a complex matrix to real if its imaginary part is all zero */
    /* ---------------------------------------------------------------------- */

    if (is_complex)
    {
        if (mtype == CHOLMOD_SPARSE)
        {
            nz = Ap [ncol] ;
        }
        else
        {
            nz = nrow * ncol ;
        }
        allzero = TRUE ;
        for (k = 0 ; k < nz ; k++)
        {
            if (Az [k] != 0)
            {
                allzero = FALSE ;
                break ;
            }
        }
        if (allzero)
        {
            /* discard the all-zero imaginary part */
            if (mtype == CHOLMOD_SPARSE)
            {
                cholmod_l_sparse_xtype (CHOLMOD_REAL, A, cm) ;
            }
            else
            {
                cholmod_l_dense_xtype (CHOLMOD_REAL, X, cm) ;
            }
        }
    }

    /* ---------------------------------------------------------------------- */
    /* return results to MATLAB */
    /* ---------------------------------------------------------------------- */

    if (mtype == CHOLMOD_SPARSE)
    {
        pargout [0] = sputil_put_sparse (&A, cm) ;
    }
    else
    {
        pargout [0] = sputil_put_dense (&X, cm) ;
    }
    if (nargout > 1)
    {
        pargout [1] = sputil_put_sparse (&Z, cm) ;
    }

    /* ---------------------------------------------------------------------- */
    /* free workspace */
    /* ---------------------------------------------------------------------- */

    cholmod_l_finish (cm) ;
    cholmod_l_print_common (" ", cm) ;
}
예제 #3
0
int main (int argc, char **argv)
{
    cholmod_sparse *A, *R ;
    cholmod_dense *B, *C ;
    SuiteSparse_long *E ;
    int mtype ;
    long m, n, rnk ;
    size_t total_mem, available_mem ;
    double t ;

    // start CHOLMOD
    cholmod_common *cc, Common ;
    cc = &Common ;
    cholmod_l_start (cc) ;

    // warmup the GPU.  This can take some time, but only needs
    // to be done once
    cc->useGPU = false ;
    t = SuiteSparse_time ( ) ;
    cholmod_l_gpu_memorysize (&total_mem, &available_mem, cc) ;
    cc->gpuMemorySize = available_mem ;
    t = SuiteSparse_time ( ) - t ;
    if (cc->gpuMemorySize <= 1)
    {
        printf ("no GPU available\n") ;
    }
    printf ("available GPU memory: %g MB, warmup time: %g\n",
        (double) (cc->gpuMemorySize) / (1024 * 1024), t) ;

    // A = mread (stdin) ; read in the sparse matrix A
    const char *filename = argv[1];
    FILE *file = fopen(filename, "r");
    A = (cholmod_sparse *) cholmod_l_read_matrix (file, 1, &mtype, cc) ;
    fclose(file);
    if (mtype != CHOLMOD_SPARSE)
    {
        printf ("input matrix must be sparse\n") ;
        exit (1) ;
    }

    // [m n] = size (A) ;
    m = A->nrow ;
    n = A->ncol ;

    long ordering = (argc < 3 ? SPQR_ORDERING_DEFAULT : atoi(argv[2]));

    printf ("Matrix %6ld-by-%-6ld nnz: %6ld\n",
        m, n, cholmod_l_nnz (A, cc)) ;

    // B = ones (m,1), a dense right-hand-side of the same type as A
    B = cholmod_l_ones (m, 1, A->xtype, cc) ;

    double tol = SPQR_NO_TOL ;
    long econ = 0 ;

    // [Q,R,E] = qr (A), but discard Q
    // SuiteSparseQR <double> (ordering, tol, econ, A, &R, &E, cc) ;

    // [C,R,E] = qr (A,b), but discard Q
    SuiteSparseQR <double> (ordering, tol, econ, A, B, &C, &R, &E, cc) ;

    // now R'*R-A(:,E)'*A(:,E) should be epsilon
    // and C = Q'*b.  The solution to the least-squares problem
    // should be x=R\C.

    // write out R to a file
    FILE *f = fopen ("R.mtx", "w") ;
    cholmod_l_write_sparse (f, R, NULL, NULL, cc) ;
    fclose (f) ;

    // write out C to a file
    f = fopen ("C.mtx", "w") ;
    cholmod_l_write_dense (f, C, NULL, cc) ;
    fclose (f) ;

    // write out E to a file
    f = fopen ("E.txt", "w") ;
    for (long i = 0 ; i < n ; i++)
    {
        fprintf (f, "%ld\n", 1 + E [i]) ;
    }
    fclose (f) ;

    // free everything
    cholmod_l_free_sparse (&A, cc) ;
    cholmod_l_free_sparse (&R, cc) ;
    cholmod_l_free_dense  (&C, cc) ;
    // cholmod_l_free (&E, cc) ;
    cholmod_l_finish (cc) ;

    return (0) ;
}
예제 #4
0
파일: chm_common.c 프로젝트: cran/Matrix
/**
 * Copy the contents of a to an appropriate CsparseMatrix object and,
 * optionally, free a or free both a and its the pointers to its contents.
 *
 * @param a  (cholmod_sparse) matrix to be converted
 * @param dofree 0 - don't free a; > 0 cholmod_free a; < 0 Free a
 * @param uploT 0 - not triangular; > 0 upper triangular; < 0 lower
 * @param Rkind - vector type to store for a->xtype == CHOLMOD_REAL,
 *                0 - REAL; 1 - LOGICAL  [unused for other a->xtype]
 * @param diag character string suitable for the diag slot of a
 *          triangular matrix (not accessed if uploT == 0).
 * @param dn either R_NilValue or an SEXP suitable for the Dimnames slot.
 *
 * @return SEXP containing a copy of a
 */
SEXP chm_sparse_to_SEXP(CHM_SP a, int dofree, int uploT, int Rkind,
			const char* diag, SEXP dn)
{
    SEXP ans;
    char *cls = "";/* -Wall */
    Rboolean longi = (a->itype) == CHOLMOD_LONG;
    int *dims, nnz, *ansp, *ansi;
    // if (longi) :
    SuiteSparse_long
	*ail = (SuiteSparse_long*)(a->i),
	*apl = (SuiteSparse_long*)(a->p);
    // else  ((a->itype) == CHOLMOD_INT) :
    int *aii = (int*)(a->i),
	*api = (int*)(a->p);

    PROTECT(dn);  /* dn is usually UNPROTECTed before the call */

				/* ensure a is sorted and packed */
    if (!a->sorted || !a->packed)
	longi ? cholmod_l_sort(a, &cl) : cholmod_sort(a, &c);
				/* determine the class of the result */

#define DOFREE_MAYBE							\
    if (dofree > 0)							\
	longi ? cholmod_l_free_sparse(&a, &cl) : cholmod_free_sparse(&a, &c); \
    else if (dofree < 0) Free(a)


    switch(a->xtype) {
    case CHOLMOD_PATTERN:
	cls = uploT ? "ntCMatrix": ((a->stype) ? "nsCMatrix" : "ngCMatrix");
	break;
    case CHOLMOD_REAL:
	switch(Rkind) {
	case 0:
	    cls = uploT ? "dtCMatrix": ((a->stype) ? "dsCMatrix" : "dgCMatrix");
	    break;
	case 1:
	    cls = uploT ? "ltCMatrix": ((a->stype) ? "lsCMatrix" : "lgCMatrix");
	    break;
	default:
	    DOFREE_MAYBE;
	    error(_("chm_sparse_to_SEXP(<real>, *): invalid 'Rkind' (real kind code)"));
	}
	break;
    case CHOLMOD_COMPLEX:
	cls = uploT ? "ztCMatrix": ((a->stype) ? "zsCMatrix" : "zgCMatrix");
	break;
    default:
	DOFREE_MAYBE;
	error(_("unknown xtype in cholmod_sparse object"));
    }
    ans = PROTECT(NEW_OBJECT_OF_CLASS(cls));
				/* allocate and copy common slots */
    nnz = longi ? cholmod_l_nnz(a, &cl) : cholmod_nnz(a, &c);
    dims = INTEGER(ALLOC_SLOT(ans, Matrix_DimSym, INTSXP, 2));
    dims[0] = a->nrow; dims[1] = a->ncol;
    ansp = INTEGER(ALLOC_SLOT(ans, Matrix_pSym, INTSXP, a->ncol + 1));
    ansi = INTEGER(ALLOC_SLOT(ans, Matrix_iSym, INTSXP, nnz));
    for (int j = 0; j <= a->ncol; j++) ansp[j] = longi ? (int)(apl[j]) : api[j];
    for (int p = 0; p < nnz; p++)      ansi[p] = longi ? (int)(ail[p]) : aii[p];
				/* copy data slot if present */
    if (a->xtype == CHOLMOD_REAL) {
	int i, *m_x;
	double *a_x = (double *) a->x;
	switch(Rkind) {
	case 0:
	    Memcpy(REAL(ALLOC_SLOT(ans, Matrix_xSym, REALSXP, nnz)),
		   a_x, nnz);
	    break;
	case 1:
	    m_x = LOGICAL(ALLOC_SLOT(ans, Matrix_xSym, LGLSXP, nnz));
	    for (i=0; i < nnz; i++)
		m_x[i] = ISNAN(a_x[i]) ? NA_LOGICAL : (a_x[i] != 0);
	    break;
	}
    }
    else if (a->xtype == CHOLMOD_COMPLEX) {
	DOFREE_MAYBE;
	error(_("complex sparse matrix code not yet written"));
/* 	Memcpy(COMPLEX(ALLOC_SLOT(ans, Matrix_xSym, CPLXSXP, nnz)), */
/* 	       (complex *) a->x, nnz); */
    }
    if (uploT) {		/* slots for triangularMatrix */
	if (a->stype) error(_("Symmetric and triangular both set"));
	SET_SLOT(ans, Matrix_uploSym, mkString((uploT > 0) ? "U" : "L"));
	SET_SLOT(ans, Matrix_diagSym, mkString(diag));
    }
    if (a->stype)		/* slot for symmetricMatrix */
	SET_SLOT(ans, Matrix_uploSym,
		 mkString((a->stype > 0) ? "U" : "L"));
    DOFREE_MAYBE;
    if (dn != R_NilValue)
	SET_SLOT(ans, Matrix_DimNamesSym, duplicate(dn));

    UNPROTECT(2);
    return ans;
}
예제 #5
0
int main (int argc, char **argv)
{
    cholmod_common Common, *cc ;
    cholmod_sparse *A ;
    cholmod_dense *X, *B ;
    int mtype ;
    Long m, n ;

    // start CHOLMOD
    cc = &Common ;
    cholmod_l_start (cc) ;

    // A = mread (stdin) ; read in the sparse matrix A
    A = (cholmod_sparse *) cholmod_l_read_matrix (stdin, 1, &mtype, cc) ;
    if (mtype != CHOLMOD_SPARSE)
    {
        printf ("input matrix must be sparse\n") ;
        exit (1) ;
    }

    // [m n] = size (A) ;
    m = A->nrow ;
    n = A->ncol ;

    printf ("Matrix %6ld-by-%-6ld nnz: %6ld\n", m, n, cholmod_l_nnz (A, cc)) ;

    // B = ones (m,1), a dense right-hand-side of the same type as A
    B = cholmod_l_ones (m, 1, A->xtype, cc) ;

    // X = A\B ; with default ordering and default column 2-norm tolerance
    if (A->xtype == CHOLMOD_REAL)
    {
        // A, X, and B are all real
        X = SuiteSparseQR <double>
            (SPQR_ORDERING_DEFAULT, SPQR_DEFAULT_TOL, A, B, cc) ;
    }
    else
    {
        // A, X, and B are all complex
        X = SuiteSparseQR < std::complex<double> >
            (SPQR_ORDERING_DEFAULT, SPQR_DEFAULT_TOL, A, B, cc) ;
    }

    check_residual (A, X, B, cc) ;
    cholmod_l_free_dense (&X, cc) ;

    // -------------------------------------------------------------------------
    // factorizing once then solving twice with different right-hand-sides
    // -------------------------------------------------------------------------

    // Just the real case.  Complex case is essentially identical
    if (A->xtype == CHOLMOD_REAL)
    {
        SuiteSparseQR_factorization <double> *QR ;
        cholmod_dense *Y ;
        Long i ;
        double *Bx ;

        // factorize once
        QR = SuiteSparseQR_factorize <double>
            (SPQR_ORDERING_DEFAULT, SPQR_DEFAULT_TOL, A, cc) ;

        // solve Ax=b, using the same B as before

        // Y = Q'*B
        Y = SuiteSparseQR_qmult (SPQR_QTX, QR, B, cc) ;
        // X = R\(E*Y)
        X = SuiteSparseQR_solve (SPQR_RETX_EQUALS_B, QR, Y, cc) ;
        // check the results
        check_residual (A, X, B, cc) ;
        // free X and Y
        cholmod_l_free_dense (&Y, cc) ;
        cholmod_l_free_dense (&X, cc) ;

        // repeat with a different B
        Bx = (double *) (B->x) ;
        for (i = 0 ; i < m ; i++)
        {
            Bx [i] = i ;
        }

        // Y = Q'*B
        Y = SuiteSparseQR_qmult (SPQR_QTX, QR, B, cc) ;
        // X = R\(E*Y)
        X = SuiteSparseQR_solve (SPQR_RETX_EQUALS_B, QR, Y, cc) ;
        // check the results
        check_residual (A, X, B, cc) ;
        // free X and Y
        cholmod_l_free_dense (&Y, cc) ;
        cholmod_l_free_dense (&X, cc) ;

        // free QR
        SuiteSparseQR_free (&QR, cc) ;
    }

    // -------------------------------------------------------------------------
    // free everything that remains
    // -------------------------------------------------------------------------

    cholmod_l_free_sparse (&A, cc) ;
    cholmod_l_free_dense (&B, cc) ;
    cholmod_l_finish (cc) ;
    return (0) ;
}
예제 #6
0
int main (int argc, char **argv)
{
    cholmod_sparse *A ;
    cholmod_dense *X, *B, *r, *atr ;
    double anorm, xnorm, rnorm, one [2] = {1,0}, minusone [2] = {-1,0}, t ;
    double zero [2] = {0,0}, atrnorm ;
    int mtype ;
    long m, n, rnk ;
    size_t total_mem, available_mem ;

    // start CHOLMOD
    cholmod_common *cc, Common ;
    cc = &Common ;
    cholmod_l_start (cc) ;

    // warmup the GPU.  This can take some time, but only needs
    // to be done once
    cc->useGPU = true ;
    t = SuiteSparse_time ( ) ;
    cholmod_l_gpu_memorysize (&total_mem, &available_mem, cc) ;
    cc->gpuMemorySize = available_mem ;
    t = SuiteSparse_time ( ) - t ;
    if (cc->gpuMemorySize <= 1)
    {
        printf ("no GPU available\n") ;
    }
    printf ("available GPU memory: %g MB, warmup time: %g\n",
        (double) (cc->gpuMemorySize) / (1024 * 1024), t) ;

    // A = mread (stdin) ; read in the sparse matrix A
    const char *filename = (argc < 2 ? "Problems/2.mtx" : argv[1]);
    FILE *file = fopen(filename, "r");
    A = (cholmod_sparse *) cholmod_l_read_matrix (file, 1, &mtype, cc) ;
    fclose(file);
    if (mtype != CHOLMOD_SPARSE)
    {
        printf ("input matrix must be sparse\n") ;
        exit (1) ;
    }

    // [m n] = size (A) ;
    m = A->nrow ;
    n = A->ncol ;

    long ordering = (argc < 3 ? SPQR_ORDERING_DEFAULT : atoi(argv[2]));

#if 1
    printf ("Matrix %6ld-by-%-6ld nnz: %6ld\n",
        m, n, cholmod_l_nnz (A, cc)) ;
#endif

    // anorm = norm (A,1) ;
    anorm = cholmod_l_norm_sparse (A, 1, cc) ;

    // B = ones (m,1), a dense right-hand-side of the same type as A
    B = cholmod_l_ones (m, 1, A->xtype, cc) ;

    // X = A\B ; with default ordering and default column 2-norm tolerance
    if (A->xtype == CHOLMOD_REAL)
    {
        // A, X, and B are all real
        X = SuiteSparseQR <double>(ordering, SPQR_NO_TOL, A, B, cc) ;
    }
    else
    {
#if SUPPORTS_COMPLEX
        // A, X, and B are all complex
        X = SuiteSparseQR < std::complex<double> >
            (SPQR_ORDERING_DEFAULT, SPQR_NO_TOL, A, B, cc) ;
#else
        printf("Code doesn't support std::complex<?> types.\n");
#endif
    }

    // get the rank(A) estimate
    rnk = cc->SPQR_istat [4] ;

    // compute the residual r, and A'*r, and their norms
    r = cholmod_l_copy_dense (B, cc) ;                  // r = B
    cholmod_l_sdmult (A, 0, one, minusone, X, r, cc) ;  // r = A*X-r = A*x-b
    rnorm = cholmod_l_norm_dense (r, 2, cc) ;           // rnorm = norm (r)
    atr = cholmod_l_zeros (n, 1, CHOLMOD_REAL, cc) ;    // atr = zeros (n,1)
    cholmod_l_sdmult (A, 1, one, zero, r, atr, cc) ;    // atr = A'*r
    atrnorm = cholmod_l_norm_dense (atr, 2, cc) ;       // atrnorm = norm (atr)

    // xnorm = norm (X)
    xnorm = cholmod_l_norm_dense (X, 2, cc) ;

    // write out X to a file
    FILE *f = fopen ("X.mtx", "w") ;
    cholmod_l_write_dense (f, X, NULL, cc) ;
    fclose (f) ;

    if (m <= n && anorm > 0 && xnorm > 0)
    {
        // find the relative residual, except for least-squares systems
        rnorm /= (anorm * xnorm) ;
    }
    printf ("\nnorm(Ax-b): %8.1e\n", rnorm) ;
    printf ("norm(A'(Ax-b))         %8.1e rank: %ld of %ld\n", 
        atrnorm, rnk, (m < n) ? m:n) ;

    /* Write an info file. */
    FILE *info = fopen("gpu_results.txt", "w");
    fprintf(info, "%ld\n", cc->SPQR_istat[7]);        // ordering method
    fprintf(info, "%ld\n", cc->memory_usage);         // memory usage (bytes)
    fprintf(info, "%30.16e\n", cc->SPQR_flopcount);   // flop count
    fprintf(info, "%lf\n", cc->SPQR_analyze_time);    // analyze time
    fprintf(info, "%lf\n", cc->SPQR_factorize_time);  // factorize time
    fprintf(info, "-1\n") ;                           // cpu memory (bytes)
    fprintf(info, "-1\n") ;                           // gpu memory (bytes)
    fprintf(info, "%32.16e\n", rnorm);                // residual
    fprintf(info, "%ld\n", cholmod_l_nnz (A, cc));    // nnz(A)
    fprintf(info, "%ld\n", cc->SPQR_istat [0]);       // nnz(R)
    fprintf(info, "%ld\n", cc->SPQR_istat [2]);       // # of frontal matrices
    fprintf(info, "%ld\n", cc->SPQR_istat [3]);       // ntasks, for now
    fprintf(info, "%lf\n", cc->gpuKernelTime);        // kernel time (ms)
    fprintf(info, "%ld\n", cc->gpuFlops);             // "actual" gpu flops
    fprintf(info, "%d\n", cc->gpuNumKernelLaunches);  // # of kernel launches
    fprintf(info, "%32.16e\n", atrnorm) ;             // norm (A'*(Ax-b))

    fclose(info);

    // free everything
    cholmod_l_free_dense (&r, cc) ;
    cholmod_l_free_dense (&atr, cc) ;
    cholmod_l_free_sparse (&A, cc) ;
    cholmod_l_free_dense (&X, cc) ;
    cholmod_l_free_dense (&B, cc) ;
    cholmod_l_finish (cc) ;

    return (0) ;
}