1 //------------------------------------------------------------------------------
2 // GB_mxm: matrix-matrix multiply for GrB_mxm, GrB_mxv, and GrB_vxm
3 //------------------------------------------------------------------------------
4 
5 // SuiteSparse:GraphBLAS, Timothy A. Davis, (c) 2017-2021, All Rights Reserved.
6 // SPDX-License-Identifier: Apache-2.0
7 
8 //------------------------------------------------------------------------------
9 
10 // C<M> = accum (C,A*B) and variations.
11 
12 // This function is not user-callable.  It does the work for user-callable
13 // functions GrB_mxm, GrB_mxv, and GrB_vxm.
14 
15 #include "GB_mxm.h"
16 #include "GB_accum_mask.h"
17 
18 #define GB_FREE_ALL         \
19 {                           \
20     GB_phbix_free (MT) ;    \
21     GB_phbix_free (T) ;     \
22 }
23 
GB_mxm(GrB_Matrix C,const bool C_replace,const GrB_Matrix M_input,const bool Mask_comp,const bool Mask_struct,const GrB_BinaryOp accum,const GrB_Semiring semiring,const GrB_Matrix A,const bool A_transpose,const GrB_Matrix B,const bool B_transpose,const bool flipxy,const GrB_Desc_Value AxB_method,const int do_sort,GB_Context Context)24 GrB_Info GB_mxm                     // C<M> = A*B
25 (
26     GrB_Matrix C,                   // input/output matrix for results
27     const bool C_replace,           // if true, clear C before writing to it
28     const GrB_Matrix M_input,       // optional mask for C, unused if NULL
29     const bool Mask_comp,           // if true, use !M
30     const bool Mask_struct,         // if true, use the only structure of M
31     const GrB_BinaryOp accum,       // optional accum for Z=accum(C,T)
32     const GrB_Semiring semiring,    // defines '+' and '*' for C=A*B
33     const GrB_Matrix A,             // input matrix
34     const bool A_transpose,         // if true, use A' instead of A
35     const GrB_Matrix B,             // input matrix
36     const bool B_transpose,         // if true, use B' instead of B
37     const bool flipxy,              // if true, do z=fmult(b,a) vs fmult(a,b)
38     const GrB_Desc_Value AxB_method,// for auto vs user selection of methods
39     const int do_sort,              // if nonzero, try to return C unjumbled
40     GB_Context Context
41 )
42 {
43 
44     //--------------------------------------------------------------------------
45     // check inputs
46     //--------------------------------------------------------------------------
47 
48     // C may be aliased with M, A, and/or B
49 
50     GrB_Info info ;
51 
52     // RMM note: header of MT and T on the stack
53     struct GB_Matrix_opaque MT_header, T_header ;
54     GrB_Matrix MT = GB_clear_static_header (&MT_header) ;
55     GrB_Matrix T  = GB_clear_static_header (&T_header) ;
56 
57     GB_RETURN_IF_FAULTY_OR_POSITIONAL (accum) ;
58     GB_RETURN_IF_NULL_OR_FAULTY (semiring) ;
59 
60     ASSERT_MATRIX_OK (C, "C input for GB_mxm", GB0) ;
61     ASSERT_MATRIX_OK_OR_NULL (M_input, "M for GB_mxm", GB0) ;
62     ASSERT_BINARYOP_OK_OR_NULL (accum, "accum for GB_mxm", GB0) ;
63     ASSERT_SEMIRING_OK (semiring, "semiring for GB_mxm", GB0) ;
64     ASSERT_MATRIX_OK (A, "A for GB_mxm", GB0) ;
65     ASSERT_MATRIX_OK (B, "B for GB_mxm", GB0) ;
66 
67     // check domains and dimensions for C<M> = accum (C,T)
68     GrB_Type T_type = semiring->add->op->ztype ;
69     GB_OK (GB_compatible (C->type, C, M_input, Mask_struct, accum, T_type,
70         Context)) ;
71 
72     // T=A*B via semiring: A and B must be compatible with semiring->multiply
73     if (flipxy)
74     {
75         // z=fmult(b,a), for entries a from A, and b from B
76         GB_OK (GB_BinaryOp_compatible (semiring->multiply,
77                 NULL, B->type, A->type, GB_ignore_code, Context)) ;
78     }
79     else
80     {
81         // z=fmult(a,b), for entries a from A, and b from B
82         GB_OK (GB_BinaryOp_compatible (semiring->multiply,
83                 NULL, A->type, B->type, GB_ignore_code, Context)) ;
84     }
85 
86     // check the dimensions
87     int64_t anrows = (A_transpose) ? GB_NCOLS (A) : GB_NROWS (A) ;
88     int64_t ancols = (A_transpose) ? GB_NROWS (A) : GB_NCOLS (A) ;
89     int64_t bnrows = (B_transpose) ? GB_NCOLS (B) : GB_NROWS (B) ;
90     int64_t bncols = (B_transpose) ? GB_NROWS (B) : GB_NCOLS (B) ;
91     if (ancols != bnrows || GB_NROWS (C) != anrows || GB_NCOLS (C) != bncols)
92     {
93         GB_ERROR (GrB_DIMENSION_MISMATCH,
94             "Dimensions not compatible:\n"
95             "output is " GBd "-by-" GBd "\n"
96             "first input is " GBd "-by-" GBd "%s\n"
97             "second input is " GBd "-by-" GBd "%s",
98             GB_NROWS (C), GB_NCOLS (C),
99             anrows, ancols, A_transpose ? " (transposed)" : "",
100             bnrows, bncols, B_transpose ? " (transposed)" : "") ;
101     }
102 
103     //--------------------------------------------------------------------------
104     // finish any pending work and check for C<!NULL> mask
105     //--------------------------------------------------------------------------
106 
107     GrB_Matrix M = M_input ;
108     GB_MATRIX_WAIT_IF_PENDING_OR_ZOMBIES (M) ;
109     if (Mask_struct && GB_is_dense (M))
110     {
111         // ignore the mask if all entries present and not complemented
112         M = NULL ;
113     }
114     // quick return if a NULL mask is complemented
115     GB_RETURN_IF_QUICK_MASK (C, C_replace, M, Mask_comp, Mask_struct) ;
116     GB_MATRIX_WAIT_IF_PENDING_OR_ZOMBIES (A) ;
117     GB_MATRIX_WAIT_IF_PENDING_OR_ZOMBIES (B) ;
118 
119     //--------------------------------------------------------------------------
120     // T = A*B, A'*B, A*B', or A'*B', also using the mask if present
121     //--------------------------------------------------------------------------
122 
123     // If C is dense (with no pending work), and the accum is present, then
124     // C+=A*B can be done in-place (C_replace is effectively false).  If C is
125     // dense, M is present, and C_replace is false, then C<M>+=A*B or
126     // C<!M>+=A*B can also be done in-place.  In all of these cases, C remains
127     // dense with all entries present.  C can have any sparsity structure;
128     // its pattern is ignored.
129 
130     // If C is bitmap, then it can always be be done in-place (assuming the
131     // type of C is OK).  The accum operator need not be present.  GB_AxB_meta
132     // can easily insert non-entries into C and check for non-entries, via the
133     // bitmap.
134 
135     // To compute C in-place, its type must match the accum->ztype, or the
136     // semiring->add->ztype if accum is not present.  To compute in-place,
137     // C must also not be transposed, and it cannot be aliased with M, A, or B.
138 
139     bool mask_applied = false ;
140     bool done_in_place = false ;
141     bool M_transposed = false ;
142     GB_OK (GB_AxB_meta (T, C, C_replace, C->is_csc, MT, &M_transposed, M,
143         Mask_comp, Mask_struct, accum, A, B, semiring, A_transpose,
144         B_transpose, flipxy, &mask_applied, &done_in_place, AxB_method,
145         do_sort, Context)) ;
146 
147     if (done_in_place)
148     {
149         // C has been computed in-place; no more work to do
150         GB_phbix_free (MT) ;
151         ASSERT_MATRIX_OK (C, "C from GB_mxm (in-place)", GB0) ;
152         return (info) ;
153     }
154 
155     ASSERT_MATRIX_OK (T, "T=A*B from GB_AxB_meta", GB0) ;
156     ASSERT_MATRIX_OK_OR_NULL (M_transposed ? MT : NULL, "MT from meta", GB0) ;
157     ASSERT (GB_ZOMBIES_OK (T)) ;
158     ASSERT (GB_JUMBLED_OK (T)) ;
159     ASSERT (!GB_PENDING (T)) ;
160 
161     //--------------------------------------------------------------------------
162     // C<M> = accum (C,T): accumulate the results into C via the mask
163     //--------------------------------------------------------------------------
164 
165     if ((accum == NULL) && (C->is_csc == T->is_csc)
166         && (M == NULL || (M != NULL && mask_applied))
167         && (C_replace || GB_NNZ_UPPER_BOUND (C) == 0))
168     {
169         // C = 0 ; C = (ctype) T ; with the same CSR/CSC format.  The mask M
170         // (if any) has already been applied.  If C is also empty, or to be
171         // cleared anyway, and if accum is not present, then T can be
172         // transplanted directly into C, as C = (ctype) T, typecasting if
173         // needed.  If no typecasting is done then this takes no time at all
174         // and is a pure transplant.  Also conform C to its desired
175         // hypersparsity.
176         GB_phbix_free (MT) ;
177         if (GB_ZOMBIES (T) && T->type != C->type)
178         {
179             // T = A*B can be constructed with zombies, using the dot3 method.
180             // Since its type differs from C, its values will be typecasted
181             // from T->type to C->type.  The zombies are killed before
182             // typecasting.  Otherwise, if they were not killed, uninitialized
183             // values in T->x for these zombies will get typecasted into C->x.
184             // Typecasting a zombie is safe, since the values of all zombies
185             // are ignored.  But valgrind complains about it, so they are
186             // killed now.  Also see the discussion in GB_transplant.
187             GBURBLE ("(wait, so zombies are not typecasted) ") ;
188             GB_OK (GB_Matrix_wait (T, "T", Context)) ;
189         }
190         GB_OK (GB_transplant_conform (C, C->type, &T, Context)) ;
191         // C may be returned with zombies and jumbled, but no pending tuples
192         ASSERT_MATRIX_OK (C, "C from GB_mxm (transplanted)", GB0) ;
193         ASSERT (GB_ZOMBIES_OK (C)) ;
194         ASSERT (GB_JUMBLED_OK (C)) ;
195         ASSERT (!GB_PENDING (C)) ;
196         return (GB_block (C, Context)) ;
197     }
198     else
199     {
200         // C<M> = accum (C,T)
201         // GB_accum_mask also conforms C to its desired hypersparsity.
202         info = GB_accum_mask (C, M, (M_transposed) ? MT : NULL, accum, &T,
203             C_replace, Mask_comp, Mask_struct, Context) ;
204         GB_phbix_free (MT) ;
205         #ifdef GB_DEBUG
206         if (info == GrB_SUCCESS)
207         {
208             // C may be returned jumbled, with zombies and pending tuples
209             ASSERT_MATRIX_OK (C, "Final C from GB_mxm (accum_mask)", GB0) ;
210             ASSERT (GB_ZOMBIES_OK (C)) ;
211             ASSERT (GB_JUMBLED_OK (C)) ;
212             ASSERT (GB_PENDING_OK (C)) ;
213         }
214         #endif
215         return (info) ;
216     }
217 }
218 
219