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