GrB_Info GB_wait // finish all pending computations ( GrB_Matrix A, // matrix with pending computations GB_Context Context ) { //-------------------------------------------------------------------------- // check inputs //-------------------------------------------------------------------------- ASSERT (A != NULL) ; // The matrix A might have pending operations but not be in the queue. // GB_check expects the matrix to be in the queue. As a result, GB_check // can report an inconsistency, and thus this assert must be made // with a negative pr. ASSERT_OK (GB_check (A, "A to wait", GB_FLIP (GB0))) ; //-------------------------------------------------------------------------- // delete zombies //-------------------------------------------------------------------------- // A zombie is an entry A(i,j) in the matrix that as been marked for // deletion, but hasn't been deleted yet. It is marked by "negating" // replacing its index i with GB_FLIP(i). Zombies are simple to delete via // an in-place algorithm. No memory is allocated so this step always // succeeds. Pending tuples are ignored, so A can have pending tuples. GrB_Info info = GrB_SUCCESS ; int64_t anz = GB_NNZ (A) ; int64_t anz_orig = anz ; int64_t anzmax_orig = A->nzmax ; ASSERT (anz_orig <= anzmax_orig) ; int64_t nzombies = A->nzombies ; if (nzombies > 0) { // There are zombies that will now be deleted. ASSERT (GB_ZOMBIES_OK (A)) ; ASSERT (GB_ZOMBIES (A)) ; // This step tolerates pending tuples // since pending tuples and zombies do not intersect ASSERT (GB_PENDING_OK (A)) ; //---------------------------------------------------------------------- // zombies exist in the matrix: delete them all //---------------------------------------------------------------------- // compare with the pruning phase of GB_resize #define GB_PRUNE if (GB_IS_ZOMBIE (i)) continue ; #include "GB_prune_inplace.c" //---------------------------------------------------------------------- // all zombies have been deleted //---------------------------------------------------------------------- // exactly A->nzombies have been deleted from A ASSERT (A->nzombies == (anz_orig - anz)) ; // at least one zombie has been deleted ASSERT (anz < anz_orig) ; // no more zombies; pending tuples may still exist A->nzombies = 0 ; ASSERT (GB_PENDING_OK (A)) ; // A->nvec_nonempty has been updated ASSERT (A->nvec_nonempty == GB_nvec_nonempty (A)) ; } ASSERT (anz == GB_NNZ (A)) ; //-------------------------------------------------------------------------- // check for pending tuples //-------------------------------------------------------------------------- // all the zombies are gone ASSERT (!GB_ZOMBIES (A)) ; if (!GB_PENDING (A)) { // nothing more to do; remove the matrix from the queue ASSERT (!GB_PENDING (A)) ; GB_CRITICAL (GB_queue_remove (A)) ; ASSERT (!(A->enqueued)) ; // trim any significant extra space from the matrix, but allow for some // future insertions. do not increase the size of the matrix; // zombies have been deleted but no pending tuples added. This is // guaranteed not to fail. ASSERT (anz <= anz_orig) ; info = GB_ix_resize (A, anz, Context) ; ASSERT (info == GrB_SUCCESS) ; // conform A to its desired hypersparsity return (GB_to_hyper_conform (A, Context)) ; } // There are pending tuples that will now be assembled. ASSERT (GB_PENDING (A)) ; //-------------------------------------------------------------------------- // construct a new hypersparse matrix T with just the pending tuples //-------------------------------------------------------------------------- // If anz > 0, T is always hypersparse. Otherwise T can be returned as // non-hypersparse, and it is then transplanted as-is into the final A. // T has the same type as A->type, which can differ from the type of the // pending tuples, A->type_pending. This is OK since build process // assembles the tuples in the order they were inserted into the matrix. // The A->operator_pending can be NULL (an implicit SECOND function), or it // can be any accum operator. The z=accum(x,y) operator can have any // types, and it does not have to be associative. GrB_Matrix T ; info = GB_builder (&T, A->type, A->vlen, A->vdim, A->is_csc, &(A->i_pending), &(A->j_pending), A->sorted_pending, A->s_pending, A->n_pending, A->max_n_pending, A->operator_pending, A->type_pending->code, Context) ; //-------------------------------------------------------------------------- // free pending tuples //-------------------------------------------------------------------------- // The tuples have been converted to T, which is more compact, and // duplicates have been removed. // This work needs to be done even if the builder fails. // GB_builder frees A->j_pending. If successful, A->i_pending is now T->i. // Otherwise A->i_pending is freed. In both cases, it has been set to NULL. ASSERT (A->i_pending == NULL && A->j_pending == NULL) ; // pending tuples are now free; so A->s_pending can be freed as well // FUTURE: GB_builder could modify A->s_pending in place to save memory, // but it can't do that for the user's S array for GrB_*_build. GB_pending_free (A) ; //-------------------------------------------------------------------------- // remove the matrix from the queue //-------------------------------------------------------------------------- ASSERT (!GB_PENDING (A)) ; ASSERT (!GB_ZOMBIES (A)) ; GB_CRITICAL (GB_queue_remove (A)) ; // No pending operations on A, and A is not in the queue, so GB_check can // now see the conditions it expects. ASSERT (!(A->enqueued)) ; ASSERT_OK (GB_check (A, "A after moving pending tuples to T", GB0)) ; //-------------------------------------------------------------------------- // check the status of the builder //-------------------------------------------------------------------------- // Finally check the status of the builder. The pending tuples, just freed // above, must be freed whether or not the builder is succesful. if (info != GrB_SUCCESS) { // out of memory GB_CONTENT_FREE (A) ; ASSERT (T == NULL) ; return (info) ; } ASSERT_OK (GB_check (T, "T = matrix of pending tuples", GB0)) ; ASSERT (!GB_PENDING (T)) ; ASSERT (!GB_ZOMBIES (T)) ; ASSERT (GB_NNZ (T) > 0) ; ASSERT (T->is_hyper) ; ASSERT (T->nvec == T->nvec_nonempty) ; //-------------------------------------------------------------------------- // check for quick transplant //-------------------------------------------------------------------------- if (anz == 0) { // A has no entries so just transplant T into A, then free T and // conform A to its desired hypersparsity. return (GB_transplant_conform (A, A->type, &T, Context)) ; } //-------------------------------------------------------------------------- // reallocate A to hold the tuples //-------------------------------------------------------------------------- // make A->nzmax larger to accomodate future tuples, but only // allocate new space if the old A->nzmax is insufficient. int64_t anz_new = anz + GB_NNZ (T) ; // must have at least this space info = GB_ix_resize (A, anz_new, Context) ; if (info != GrB_SUCCESS) { // out of memory GB_MATRIX_FREE (&T) ; return (info) ; } //-------------------------------------------------------------------------- // if A is hypersparse, ensure A->plen is sufficient for A=A+T //-------------------------------------------------------------------------- // If anz > 0, T is hypersparse, even if A is a GrB_Vector ASSERT (T->is_hyper) ; // No addition is done since the nonzero patterns of A and T are disjoint. int64_t *restrict Ah = A->h ; int64_t *restrict Ap = A->p ; int64_t *restrict Ai = A->i ; GB_void *restrict Ax = A->x ; int64_t anvec = A->nvec ; int64_t anvec_new = anvec ; const int64_t *restrict Th = T->h ; const int64_t *restrict Tp = T->p ; const int64_t *restrict Ti = T->i ; const GB_void *restrict Tx = T->x ; int64_t tnvec = T->nvec ; int64_t ak, tk ; if (A->is_hyper) { // 2-way merge of A->h and T->h for (ak = 0, tk = 0 ; ak < anvec && tk < tnvec ; ) { int64_t ja = Ah [ak] ; int64_t jt = Th [tk] ; if (jt == ja) { // vector jt appears in both A and T ak++ ; tk++ ; } else if (ja < jt) { // vector ja appears in A but not T ak++ ; } else // jt < ja { // vector jt appears in T but not A tk++ ; anvec_new++ ; } } // count the vectors not yet seen in T if (tk < tnvec) { anvec_new += (tnvec - tk) ; } // reallocate A->p and A->h, if needed if (anvec_new > A->plen) { if (GB_to_nonhyper_test (A, anvec_new, A->vdim)) { // convert to non-hypersparse if anvec_new will become too large info = GB_to_nonhyper (A, Context) ; } else { // increase the size of A->p and A->h. The size must be at // least anvec_new, but add some slack for future growth. int64_t aplen_new = 2 * (anvec_new + 1) ; aplen_new = GB_IMIN (aplen_new, A->vdim) ; info = GB_hyper_realloc (A, aplen_new, Context) ; } if (info != GrB_SUCCESS) { // out of memory; all content of A has been freed ASSERT (A->magic == GB_MAGIC2) ; GB_MATRIX_FREE (&T) ; return (info) ; } Ah = A->h ; Ap = A->p ; } } ASSERT_OK (GB_check (A, "A after increasing A->h", GB0)) ; ASSERT_OK (GB_check (T, "T to fold in", GB0)) ; //-------------------------------------------------------------------------- // A = A + T ; in place by folding in the tuples in reverse order //-------------------------------------------------------------------------- // Merge in the tuples into each vector, in reverse order. Note that Ap // [k+1] or Ap [j+1] is changed during the iteration. The bottom of the // new A is treated like a stack, where entries are placed on top of the Ai // and Ax stack, and vector indices are placed on top of the Ah stack // if A is hypersparse. // T is always hypersparse, even if A and T are typecasted GrB_Vector // objects. A can be non-hypersparse or hypersparse. If A is hypersparse // then this step does not take O(A->vdim) time. It takes at most // O(nnz(Z)+nnz(A)) time, regardless of the vector dimension of A and T, // A->vdim and T->vdim. bool A_is_hyper = A->is_hyper ; int64_t asize = A->type->size ; // pdest points to the top of the stack at the end of the A matrix; // this is also the total number of nonzeros in A+T. Since the stack // is empty, pdest points to one past the position where the last entry // in A will appear. int64_t pdest = anz_new ; // pdest-1 must be within the size of A->i and A->x ASSERT (pdest <= A->nzmax) ; tk = tnvec - 1 ; // ak_dest points to the top of the hyperlist stack, also currently empty. int64_t ak_dest ; if (A_is_hyper) { // Ah [ak] is the rightmost non-empty vector in the hypersparse A. // It will be moved to Ah [anvec_new-1]. ak = A->nvec - 1 ; ak_dest = anvec_new ; } else { // ak is the rightmost vector in the non-hypersparse A ak = A->vdim - 1 ; ak_dest = A->vdim ; ASSERT (A->nvec == A->vdim) ; } // count the number of non-empty vectors (again, if hypersparse, but for // the first time if non-hypersparse) anvec_new = A->nvec_nonempty ; // while T has non-empty vectors while (tk >= 0) { // When T is exhausted, the while loop can stop. Let j1 be the // leftmost non-empty vector of the hypersparse T. A(:,0:j1-1) is // not affected by the merge. Only vectors A(:,j1:n-1) need to be // shifted (where n == A->vdim). // If the vectors of A are exhausted, ak becomes -1 and stays there. ASSERT (ak >= -1) ; //---------------------------------------------------------------------- // get vectors A(:,j) and T(:,j) //---------------------------------------------------------------------- int64_t j, ja, jt, pa, pa_end, pt, pt_end ; if (A_is_hyper) { // get the next non-empty vector ja in the prior hypersparse A ja = (ak >= 0) ? Ah [ak] : -1 ; } else { // ja always appears in the non-hypersparse A ja = ak ; } // jt is the next non-empty vector in the hypersparse T jt = Th [tk] ; ASSERT (jt >= 0) ; ASSERT (ja >= -1) ; if (ja == jt) { // vector j appears in both A(:,j) and T(:,j) ASSERT (ak >= 0) ; j = ja ; pa_end = Ap [ak ] - 1 ; pa = Ap [ak+1] - 1 ; pt_end = Tp [tk ] - 1 ; pt = Tp [tk+1] - 1 ; } else if (ja > jt) { // vector j appears in A(:,j) but not T(:,j) ASSERT (ak >= 0) ; j = ja ; pa_end = Ap [ak ] - 1 ; pa = Ap [ak+1] - 1 ; pt_end = -1 ; pt = -1 ; } else // jt > ja { // vector j appears in T(:,j) but not A(:,j) ASSERT (ak >= -1) ; j = jt ; pa_end = -1 ; pa = -1 ; pt_end = Tp [tk ] - 1 ; pt = Tp [tk+1] - 1 ; } ASSERT (j >= 0 && j < A->vdim) ; // A (:,j) is in Ai,Ax [pa_end+1 ... pa] // T (:,j) is in Ti,Tx [pt_end+1 ... pt] //---------------------------------------------------------------------- // count the number of non-empty vectors in the new A //---------------------------------------------------------------------- if (!(pa > pa_end) && (pt > pt_end)) { // A(:,j) is empty but T(:,j) is not; count one more non-empty // vector in A anvec_new++ ; } //---------------------------------------------------------------------- // log the new end of A(:,j) //---------------------------------------------------------------------- // get the next free slot on the hyperlist stack ASSERT (ak < ak_dest) ; ASSERT (GB_IMPLIES (!A_is_hyper, ak_dest == ak+1)) ; --ak_dest ; ASSERT (ak <= ak_dest) ; ASSERT (ak_dest >= 0) ; if (A_is_hyper) { // push j onto the stack for the new hyperlist for A Ah [ak_dest] = j ; } ASSERT (GB_IMPLIES (!A_is_hyper, ak_dest == ak && j == ak)) ; Ap [ak_dest+1] = pdest ; //---------------------------------------------------------------------- // merge while entries exist in both A (:,j) and T (:,j) (reverse order) //---------------------------------------------------------------------- while (pa > pa_end && pt > pt_end) { // entries exist in both A (:,j) and T (:,j); take the biggest one int64_t ia = Ai [pa] ; int64_t it = Ti [pt] ; // no entries are both in A and T ASSERT (ia != it) ; // get next free slot on the top of the stack of the entries of A --pdest ; ASSERT (pa < pdest) ; if (ia > it) { // push Ai,Ax [pa] onto the stack Ai [pdest] = ia ; // Ax [pdest] = Ax [pa] memcpy (Ax +(pdest*asize), Ax +(pa*asize), asize) ; --pa ; } else // it > ia { // push Ti,Tx [pt] onto the stack ASSERT (it > ia) ; Ai [pdest] = it ; // Ax [pdest] = Tx [pt] memcpy (Ax +(pdest*asize), Tx +(pt*asize), asize) ; --pt ; } } //---------------------------------------------------------------------- // merge the remainder //---------------------------------------------------------------------- // Either A (:,j) or T (:,j) is exhausted; but the other one can have // entries that still need to be shifted down. // FUTURE: can use two memmove's here, for Ai and Ax, with no while // loop, since the source and destination can overlap while (pa > pa_end) { // entries still exist in A (:,j); shift downwards int64_t ia = Ai [pa] ; // get next free slot on the top of the stack of the entries of A --pdest ; ASSERT (pa <= pdest) ; // push Ai,Ax [pa] onto the stack if (pa != pdest) { Ai [pdest] = ia ; // Ax [pdest] = Ax [pa] memcpy (Ax +(pdest*asize), Ax +(pa*asize), asize) ; } --pa ; } // FUTURE: can use two memcpy's here, for Ai and Ax, with no while loop while (pt > pt_end) { // entries still exist in T (:,j); shift downwards int64_t it = Ti [pt] ; // get next free slot on the top of the stack of the entries of A --pdest ; // push Ti,Tx [pt] onto the stack Ai [pdest] = it ; // Ax [pdest] = Tx [pt] memcpy (Ax +(pdest*asize), Tx +(pt*asize), asize) ; --pt ; } //---------------------------------------------------------------------- // advance to the next vector (right to left) //---------------------------------------------------------------------- if (ja == jt) { // vector j appears in both A(:,j) and T(:,j) --ak ; --tk ; } else if (ja > jt) { // vector j appears in A(:,j) but not T(:,j) --ak ; } else // jt > ja { // vector j appears in T(:,j) but not A(:,j) --tk ; } } // update the count of non-empty vectors in A A->nvec_nonempty = anvec_new ; // all vectors have been merged into A if (A->is_hyper) { A->nvec = A->nvec_nonempty ; } // end condition: no need to log the end of A(:,-1) since Ap[0]=0 // already holds. ASSERT (Ap [0] == 0) ; //-------------------------------------------------------------------------- // tuples have now been assembled into the matrix //-------------------------------------------------------------------------- GB_MATRIX_FREE (&T) ; ASSERT_OK (GB_check (A, "A after assembling pending tuples", GB0)) ; // conform A to its desired hypersparsity return (GB_to_hyper_conform (A, Context)) ; }
GrB_Info GB_matvec_check // check a GraphBLAS matrix or vector ( const GrB_Matrix A, // GraphBLAS matrix to print and check const char *name, // name of the matrix, optional int pr, // 0: print nothing, 1: print header and errors, // 2: print brief, 3: print all // if negative, ignore queue conditions // and use GB_FLIP(pr) for diagnostic printing. FILE *f, // file for output const char *kind, // "matrix" or "vector" GB_Context Context ) { //-------------------------------------------------------------------------- // check inputs //-------------------------------------------------------------------------- bool ignore_queue = false ; if (pr < 0) { // -2: print nothing (pr = 0) // -3: print header (pr = 1) // -4: print brief (pr = 2) // -5: print all (pr = 3) pr = GB_FLIP (pr) ; ignore_queue = true ; } if (pr > 0) GBPR ("\nGraphBLAS %s: %s ", kind, GB_NAME) ; if (A == NULL) { // GrB_error status not modified since this may be an optional argument if (pr > 0) GBPR ("NULL\n") ; return (GrB_NULL_POINTER) ; } //-------------------------------------------------------------------------- // check the object //-------------------------------------------------------------------------- GB_CHECK_MAGIC (A, kind) ; ASSERT (A->magic == GB_MAGIC) ; // A is now a valid initialized object if (pr > 0) { GBPR ("\nnrows: "GBd" ncols: "GBd" max # entries: "GBd"\n", GB_NROWS (A), GB_NCOLS (A), A->nzmax) ; GBPR ("format: %s %s", A->is_hyper ? "hypersparse" : "standard", A->is_csc ? "CSC" : "CSR") ; GBPR (" vlen: "GBd" nvec_nonempty: "GBd" nvec: "GBd" plen: " GBd " vdim: "GBd"\n", A->vlen, A->nvec_nonempty, A->nvec, A->plen, A->vdim) ; GBPR ("hyper_ratio %g\n", A->hyper_ratio) ; } if (A->vlen < 0 || A->vlen > GB_INDEX_MAX || A->vdim < 0 || A->vdim > GB_INDEX_MAX || A->nzmax < 0 || A->nzmax > GB_INDEX_MAX) { if (pr > 0) GBPR ("invalid %s dimensions\n", kind) ; return (GB_ERROR (GrB_INVALID_OBJECT, (GB_LOG, "%s invalid : nrows, ncols, or nzmax out of range: [%s]", kind, GB_NAME))) ; } if (A->is_hyper) { if (! (A->nvec >= 0 && A->nvec <= A->plen && A->plen <= A->vdim && A->nvec == A->nvec_nonempty)) { if (pr > 0) GBPR ("invalid %s hypersparse structure\n", kind) ; return (GB_ERROR (GrB_INVALID_OBJECT, (GB_LOG, "%s invalid hypersparse structure [%s]", kind, GB_NAME))) ; } } else { if (! (A->nvec == A->plen && A->plen == A->vdim)) { if (pr > 0) GBPR ("invalid %s standard structure\n", kind) ; return (GB_ERROR (GrB_INVALID_OBJECT, (GB_LOG, "%s invalid structure [%s]", kind, GB_NAME))) ; } } // a matrix contains 1 to 8 different malloc'd blocks int64_t nallocs = 1 + // header (A->h != NULL && !A->h_shallow) + // A->h, if not shallow (A->p != NULL && !A->p_shallow) + // A->p, if not shallow (A->i != NULL && !A->i_shallow) + // A->i, if not shallow (A->x != NULL && !A->x_shallow) + // A->x, if not shallow (A->i_pending != NULL) + // A->i_pending if tuples (A->j_pending != NULL) + // A->j_pending if tuples (A->s_pending != NULL) ; // A->s_pending if tuples #ifdef GB_DEVELOPER if (pr > 1) GBPR ("A %p magic "GBd"\n", A, A->magic) ; if (pr > 1) GBPR ("number of memory blocks: "GBd"\n", nallocs) ; #endif GrB_Info info = GB_Type_check (A->type, "", pr, f, Context) ; if (info != GrB_SUCCESS || (A->type->size != A->type_size)) { if (pr > 0) GBPR ("%s has an invalid type\n", kind) ; return (GB_ERROR (GrB_INVALID_OBJECT, (GB_LOG, "%s has an invalid type: [%s]", kind, GB_NAME))) ; } if (A->Sauna != NULL) { if (pr > 1) GBPR ("Sauna: n: "GBd" entry size: %zu\n", A->Sauna->Sauna_n, A->Sauna->Sauna_size) ; } if (pr > 1 && A->AxB_method_used != GxB_DEFAULT) { GBPR ("last method used for GrB_mxm, vxm, or mxv: ") ; switch (A->AxB_method_used) { case GxB_AxB_GUSTAVSON : GBPR ("Gustavson") ; break ; case GxB_AxB_HEAP : GBPR ("heap") ; break ; case GxB_AxB_DOT : GBPR ("dot") ; break ; default: ; } GBPR ("\n") ; } #ifdef GB_DEVELOPER if (pr > 1) GBPR ("->h: %p shallow: %d\n", A->h, A->h_shallow) ; if (pr > 1) GBPR ("->p: %p shallow: %d\n", A->p, A->p_shallow) ; if (pr > 1) GBPR ("->i: %p shallow: %d\n", A->i, A->i_shallow) ; if (pr > 1) GBPR ("->x: %p shallow: %d\n", A->x, A->x_shallow) ; #endif if (A->p == NULL) { if (pr > 0) GBPR ("->p is NULL, invalid %s\n", kind) ; return (GB_ERROR (GrB_INVALID_OBJECT, (GB_LOG, "%s contains a NULL A->p pointer: [%s]", kind, GB_NAME))) ; } if (A->is_hyper) { if (A->h == NULL) { if (pr > 0) GBPR ("->h is NULL, invalid hypersparse %s\n", kind) ; return (GB_ERROR (GrB_INVALID_OBJECT, (GB_LOG, "hypersparse %s contains a NULL A->h pointer: [%s]", kind, GB_NAME))) ; } } else { if (A->h != NULL) { if (pr > 0) GBPR ("->h is not NULL, invalid non-hypersparse %s\n", kind) ; return (GB_ERROR (GrB_INVALID_OBJECT, (GB_LOG, "non-hypersparse %s contains a non-NULL A->h pointer: [%s]", kind, GB_NAME))) ; } } bool A_empty = (A->nzmax == 0) ; if (A_empty) { // A->x and A->i pointers must be NULL and shallow must be false if (A->i != NULL || A->i_shallow || A->x_shallow) { if (pr > 0) GBPR ("invalid empty %s\n", kind) ; return (GB_ERROR (GrB_INVALID_OBJECT, (GB_LOG, "%s is an invalid empty object: [%s]", kind, GB_NAME))) ; } // check the vector pointers for (int64_t j = 0 ; j <= A->nvec ; j++) { if (A->p [j] != 0) { if (pr > 0) GBPR ("->p ["GBd"] = "GBd" invalid\n", j,A->p[j]); return (GB_ERROR (GrB_INVALID_OBJECT, (GB_LOG, "%s ->p ["GBd"] = "GBd" invalid: [%s]", kind, j, A->p[j], GB_NAME))) ; } } if (pr > 0) GBPR ("empty\n") ; } if (!A_empty && A->i == NULL) { if (pr > 0) GBPR ("->i is NULL, invalid %s\n", kind) ; return (GB_ERROR (GrB_INVALID_OBJECT, (GB_LOG, "%s contains a NULL A->i pointer: [%s]", kind, GB_NAME))) ; } //-------------------------------------------------------------------------- // check the vector pointers //-------------------------------------------------------------------------- if (A->p [0] != 0) { if (pr > 0) GBPR ("->p [0] = "GBd" invalid\n", A->p [0]) ; return (GB_ERROR (GrB_INVALID_OBJECT, (GB_LOG, "%s A->p [0] = "GBd" invalid: [%s]", kind, A->p [0], GB_NAME))) ; } for (int64_t j = 0 ; j < A->nvec ; j++) { if (A->p [j+1] < A->p [j] || A->p [j+1] > A->nzmax) { if (pr > 0) GBPR ("->p ["GBd"] = "GBd" invalid\n", j+1, A->p [j+1]) ; return (GB_ERROR (GrB_INVALID_OBJECT, (GB_LOG, "%s A->p ["GBd"] = "GBd" invalid: [%s]", kind, j+1, A->p [j+1], GB_NAME))) ; } } if (A->is_hyper) { int64_t jlast = -1 ; for (int64_t k = 0 ; k < A->nvec ; k++) { int64_t j = A->h [k] ; if (jlast >= j || j < 0 || j >= A->vdim) { if (pr > 0) GBPR ("->h ["GBd"] = "GBd" invalid\n", k, A->h [k]) ; return (GB_ERROR (GrB_INVALID_OBJECT, (GB_LOG, "%s A->h ["GBd"] = "GBd" invalid: [%s]", kind, k, A->h [k], GB_NAME))) ; } jlast = j ; } } int64_t anz = GB_NNZ (A) ; if (pr > 0) GBPR ("number of entries: "GBd" ", anz) ; if (pr > 0) GBPR ("\n") ; //-------------------------------------------------------------------------- // report the number of pending tuples and number of zombies //-------------------------------------------------------------------------- if (A->n_pending != 0 || A->nzombies != 0) { if (pr > 0) GBPR ("pending tuples: "GBd" max pending: "GBd " zombies: "GBd"\n", A->n_pending, A->max_n_pending, A->nzombies) ; } if (A->nzombies < 0 || A->nzombies > anz) { if (pr > 0) GBPR ("invalid number of zombies: "GBd" " "must be >= 0 and <= # entries ("GBd")\n", A->nzombies, anz) ; return (GB_ERROR (GrB_INVALID_OBJECT, (GB_LOG, "%s invalid number of zombies: "GBd"\n" "must be >= 0 and <= # entries ("GBd") [%s]", kind, A->nzombies, anz, GB_NAME))) ; } //-------------------------------------------------------------------------- // check and print the row indices and numerical values //-------------------------------------------------------------------------- #define GB_NBRIEF 10 #define GB_NZBRIEF 30 bool jumbled = false ; int64_t nzombies = 0 ; int64_t jcount = 0 ; GB_for_each_vector (A) { int64_t ilast = -1 ; GB_for_each_entry (j, p, pend) { bool prcol = ((pr > 1 && jcount < GB_NBRIEF) || pr > 2) ; if (ilast == -1) { // print the header for vector j if (prcol) { GBPR ("%s: "GBd" : "GBd" entries ["GBd":"GBd"]\n", A->is_csc ? "column" : "row", j, pend - p, p, pend-1) ; } else if (pr == 2 && jcount == GB_NBRIEF) { GBPR ("...\n") ; } jcount++ ; // count # of vectors printed so far } int64_t i = A->i [p] ; bool is_zombie = GB_IS_ZOMBIE (i) ; i = GB_UNFLIP (i) ; if (is_zombie) nzombies++ ; if (prcol) { if ((pr > 1 && p < GB_NZBRIEF) || pr > 2) { GBPR (" %s "GBd": ", A->is_csc ? "row" : "column", i) ; } else if (pr == 2 && (ilast == -1 || p == GB_NZBRIEF)) { GBPR (" ...\n") ; } } int64_t row = A->is_csc ? i : j ; int64_t col = A->is_csc ? j : i ; if (i < 0 || i >= A->vlen) { if (pr > 0) GBPR ("index ("GBd","GBd") out of range\n", row, col) ; return (GB_ERROR (GrB_INVALID_OBJECT, (GB_LOG, "%s index ("GBd","GBd") out of range: [%s]", kind, row, col, GB_NAME))) ; } // print the value bool print_value = prcol && ((pr > 1 && p < GB_NZBRIEF) || pr > 2) ; if (print_value) { if (is_zombie) { GBPR ("zombie") ; } else if (A->x != NULL) { GB_void *Ax = A->x ; info = GB_entry_check (A->type, Ax +(p * (A->type->size)), f, Context) ; if (info != GrB_SUCCESS) return (info) ; } } if (i <= ilast) { // indices unsorted, or duplicates present if (pr > 0) GBPR (" index ("GBd","GBd") jumbled", row, col) ; jumbled = true ; print_value = (pr > 0) ; } if (print_value) { GBPR ("\n") ; } ilast = i ; } }