1 //------------------------------------------------------------------------------
2 // GB_mx_mxArray_to_Matrix
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 // Convert a MATLAB sparse or full matrix, or a struct to a GraphBLAS sparse
11 // matrix.  The mxArray is either a struct containing two terms: a sparse or
12 // full matrix or vector, and type (a string, "logical", "double", etc), or it
13 // is just a plain sparse or full matrix.  If A.class is present, it is used to
14 // typecast the MATLAB matrix into the corresponding type in GraphBLAS.
15 
16 // That is:
17 // A = sparse (...) ;   % a sparse double or logical GraphBLAS matrix
18 
19 // A.matrix = A ; A.class = 'int8' ; Represents a MATLAB sparse or full matrix
20 // that represents a GraphBLAS int8 matrix.  On input, the MATLAB sparse or
21 // full matrix is typecasted.
22 
23 // The MATLAB matrix or struct is not modified.  If deep_copy is true, the
24 // GraphBLAS matrix is always a deep copy and can be modified by GraphBLAS.
25 // Otherwise, its pattern (A->p, A->h, and A->i) may be a shallow copy, and
26 // A->x is a shallow copy if the MATLAB matrix is 'logical' or 'double'.
27 
28 // If the MATLAB matrix is double complex, it becomes a GraphBLAS
29 // Complex or GxB_FC64 matrix.
30 
31 // A->x is always a deep copy for other types, since it must be typecasted from
32 // MATLAB to GraphBLAS.
33 
34 // Like GB_mx_Matrix_to_mxArray, this could be done using only user-callable
35 // GraphBLAS functions, but the method used here is faster.
36 
37 // A.sparsity sets the GxB_SPARSITY_CONTROL option: 0 to 15 (see GB_conform.c),
38 // which is any sum of these 4 flags:
39 //
40 //    // GxB_SPARSITY_CONTROL can be any sum or bitwise OR of these 4 values:
41 //    #define GxB_HYPERSPARSE 1   // hypersparse form
42 //    #define GxB_SPARSE      2   // sparse form
43 //    #define GxB_BITMAP      4   // a bitmap
44 //    #define GxB_FULL        8   // full (all entries must be present)
45 
46 #include "GB_mex.h"
47 
48 #define FREE_ALL            \
49 {                           \
50     GrB_Matrix_free_(&A) ;  \
51 }
52 
GB_mx_mxArray_to_Matrix(const mxArray * A_matlab,const char * name,bool deep_copy,const bool empty)53 GrB_Matrix GB_mx_mxArray_to_Matrix     // returns GraphBLAS version of A
54 (
55     const mxArray *A_matlab,            // MATLAB version of A
56     const char *name,                   // name of the argument
57     bool deep_copy,                     // if true, return a deep copy
58     const bool empty    // if false, 0-by-0 matrices are returned as NULL.
59                         // if true, a 0-by-0 matrix is returned.
60 )
61 {
62 
63     //--------------------------------------------------------------------------
64     // check for empty matrix
65     //--------------------------------------------------------------------------
66 
67     GB_CONTEXT ("mxArray_to_Matrix") ;
68 
69     GrB_Matrix A = NULL ;
70 
71     if (A_matlab == NULL)
72     {
73         // input is not present; this is not an error if A is an
74         // optional input
75         return (NULL) ;
76     }
77 
78     if ((mxGetM (A_matlab) == 0) && (mxGetN (A_matlab) == 0))
79     {
80         // input is "[ ]", zero-by-zero.
81         if (empty)
82         {
83             // treat as a sparse 0-by-0 matrix, not NULL
84             GrB_Matrix_new (&A, GrB_FP64, 0, 0) ;
85             ASSERT_MATRIX_OK (A, "got A = [ ] from MATLAB", GB0) ;
86             return (A) ;
87         }
88         else
89         {
90             // Treat as NULL in GraphBLAS.  Useful for mask matrices
91             return (NULL) ;
92         }
93     }
94 
95     //--------------------------------------------------------------------------
96     // get the matrix
97     //--------------------------------------------------------------------------
98 
99     const mxArray *Amatrix = NULL ;
100     GrB_Type atype_in, atype_out ;
101     GB_Type_code atype_in_code, atype_out_code ;
102 
103     if (mxIsStruct (A_matlab))
104     {
105         // look for A.matrix
106         int fieldnumber = mxGetFieldNumber (A_matlab, "matrix") ;
107         if (fieldnumber >= 0)
108         {
109             Amatrix = mxGetFieldByNumber (A_matlab, 0, fieldnumber) ;
110         }
111         else
112         {
113             // A.matrix not present, try A.vector
114             fieldnumber = mxGetFieldNumber (A_matlab, "vector") ;
115             if (fieldnumber < 0)
116             {
117                 FREE_ALL ;
118                 mexWarnMsgIdAndTxt ("GB:warn", "invalid matrix/vector struct") ;
119                 return (NULL) ;
120             }
121             Amatrix = mxGetFieldByNumber (A_matlab, 0, fieldnumber) ;
122             if (mxGetN (Amatrix) != 1)
123             {
124                 FREE_ALL ;
125                 mexWarnMsgIdAndTxt ("GB:warn", "vector must be n-by-1") ;
126                 return (NULL) ;
127             }
128         }
129 
130         // get the type
131         ASSERT (Amatrix != NULL) ;
132 
133         atype_in = GB_mx_Type (Amatrix) ;
134         atype_out = atype_in ;
135         fieldnumber = mxGetFieldNumber (A_matlab, "class") ;
136         if (fieldnumber >= 0)
137         {
138             mxArray *s = mxGetFieldByNumber (A_matlab, 0, fieldnumber) ;
139             atype_out = GB_mx_string_to_Type (s, atype_in) ;
140         }
141     }
142     else
143     {
144         // just a matrix
145         Amatrix = A_matlab ;
146         atype_in = GB_mx_Type (Amatrix) ;
147         atype_out = atype_in ;
148     }
149 
150     bool A_is_sparse = mxIsSparse (Amatrix) ;
151 
152     //--------------------------------------------------------------------------
153     // get the matrix type
154     //--------------------------------------------------------------------------
155 
156     atype_in_code  = atype_in->code ;
157     atype_out_code = atype_out->code ;
158 
159     //--------------------------------------------------------------------------
160     // get the size and content of the MATLAB matrix
161     //--------------------------------------------------------------------------
162 
163     int64_t nrows = mxGetM (Amatrix) ;
164     int64_t ncols = mxGetN (Amatrix) ;
165     int64_t *Mp, *Mi, anz, anzmax ;
166 
167     if (A_is_sparse)
168     {
169         Mp = (int64_t *) mxGetJc (Amatrix) ;
170         Mi = (int64_t *) mxGetIr (Amatrix) ;
171         anz = Mp [ncols] ;
172         anzmax = mxGetNzmax (Amatrix) ;
173     }
174     else
175     {
176         Mp = NULL ;
177         Mi = NULL ;
178         anz = nrows * ncols ;
179         anzmax = anz ;
180     }
181 
182     GB_void *Mx = mxGetData (Amatrix) ;
183 
184     //--------------------------------------------------------------------------
185     // look for A.values
186     //--------------------------------------------------------------------------
187 
188     if (mxIsStruct (A_matlab))
189     {
190         // this is used for int64 and uint64 only
191         int fieldnumber = mxGetFieldNumber (A_matlab, "values") ;
192         if (fieldnumber >= 0)
193         {
194             mxArray *values = mxGetFieldByNumber (A_matlab, 0, fieldnumber) ;
195             if (mxIsComplex (values))
196             {
197                 mexErrMsgTxt ("A.values must be real") ;
198             }
199             if (mxGetNumberOfElements (values) >= anz)
200             {
201                 Mx = mxGetData (values) ;
202                 atype_in = GB_mx_Type (values) ;
203                 atype_in_code = atype_in->code ;
204                 anzmax = mxGetNumberOfElements (values) ;
205             }
206         }
207     }
208 
209     ASSERT_TYPE_OK (atype_in,  "A type in", GB0) ;
210     ASSERT_TYPE_OK (atype_out, "A type out", GB0) ;
211 
212     if (atype_in == NULL || atype_out == NULL)
213     {
214         FREE_ALL ;
215         mexWarnMsgIdAndTxt ("GB:warn", "types must be numeric") ;
216         return (NULL) ;
217     }
218 
219     GrB_Info info ;
220 
221     // MATLAB matrices are sparse or full CSC, not hypersparse or bitmap
222     bool is_csc = true ;
223     int sparsity = (A_is_sparse) ? GxB_SPARSE : GxB_FULL ;
224 
225     //--------------------------------------------------------------------------
226     // get the pattern of A
227     //--------------------------------------------------------------------------
228 
229     if (deep_copy)
230     {
231 
232         // create the GraphBLAS matrix
233         info = GB_new (&A, false, // sparse or full, new mx header
234             atype_out, (GrB_Index) nrows, (GrB_Index) ncols,
235             GB_Ap_calloc, is_csc, sparsity, GxB_HYPER_DEFAULT, 0, Context) ;
236         if (info != GrB_SUCCESS)
237         {
238             FREE_ALL ;
239             mexWarnMsgIdAndTxt ("GB:warn", "new deep matrix failed") ;
240             return (NULL) ;
241         }
242 
243         // A is a deep copy and can be modified by GraphBLAS
244         info = GB_bix_alloc (A, anz, false, false, sparsity != GxB_FULL, true,
245             Context) ;
246         if (info != GrB_SUCCESS)
247         {
248             FREE_ALL ;
249             mexWarnMsgIdAndTxt ("GB:warn", "out of memory") ;
250             return (NULL) ;
251         }
252 
253         if (sparsity != GxB_FULL)
254         {
255             memcpy (A->p, Mp, (ncols+1) * sizeof (int64_t)) ;
256             memcpy (A->i, Mi, anz * sizeof (int64_t)) ;
257         }
258         A->magic = GB_MAGIC ;
259 
260     }
261     else
262     {
263 
264         // the GraphBLAS pattern (A->p and A->i) are pointers into the
265         // MATLAB matrix and must not be modified.
266 
267         // [ create the GraphBLAS matrix, do not allocate A->p
268         info = GB_new (&A, false, // sparse or full, new mx header
269             atype_out, (GrB_Index) nrows, (GrB_Index) ncols,
270             GB_Ap_null, is_csc, sparsity, GxB_HYPER_DEFAULT, 0, Context) ;
271         if (info != GrB_SUCCESS)
272         {
273             FREE_ALL ;
274             mexWarnMsgIdAndTxt ("GB:warn", "new shallow matrix failed") ;
275             return (NULL) ;
276         }
277 
278         A->p_size = 0 ;
279         A->i_size = 0 ;
280 
281         if (sparsity != GxB_FULL)
282         {
283             A->p = Mp ;
284             A->i = Mi ;
285             A->p_shallow = true ;
286             A->i_shallow = true ;
287         }
288         else
289         {
290             A->p = NULL ;
291             A->i = NULL ;
292             A->p_shallow = false ;
293             A->i_shallow = false ;
294         }
295 
296         A->h_shallow = false ;      // A->h is NULL
297         A->magic = GB_MAGIC ;       // A->p now initialized ]
298     }
299 
300     //--------------------------------------------------------------------------
301     // copy the numerical values from MATLAB to the GraphBLAS matrix
302     //--------------------------------------------------------------------------
303 
304     if (sparsity == GxB_FULL)
305     {
306         A->x_shallow = (!deep_copy && (atype_out_code == atype_in_code)) ;
307     }
308     else
309     {
310         A->x_shallow = (!deep_copy &&
311                ((atype_out_code == GB_BOOL_code ||
312                  atype_out_code == GB_FP64_code ||
313                  atype_out_code == GB_FC64_code)
314              && (atype_out_code == atype_in_code))) ;
315     }
316 
317     if (A->x_shallow)
318     {
319         // the MATLAB matrix and GraphBLAS matrix have the same type; (logical,
320         // double, or double complex), and a deep copy is not requested.  Just
321         // make a shallow copy.
322         A->nzmax = anzmax ;
323         A->x = Mx ;
324         A->x_size = 0 ;     // A->x is shallow
325     }
326     else
327     {
328         if (!deep_copy)
329         {
330             // allocate new space for the GraphBLAS values
331             A->nzmax = GB_IMAX (anz, 1) ;
332             A->x = (GB_void *) GB_malloc_memory (A->nzmax * atype_out->size,
333                 sizeof (GB_void), &(A->x_size)) ;
334             if (A->x == NULL)
335             {
336                 FREE_ALL ;
337                 mexWarnMsgIdAndTxt ("GB:warn", "out of memory") ;
338                 return (NULL) ;
339             }
340         }
341 
342         GB_cast_array (
343             A->x,
344             (atype_out_code == GB_UDT_code) ? GB_FC64_code : atype_out_code,
345             Mx,
346             (atype_in_code == GB_UDT_code) ? GB_FC64_code : atype_in_code,
347             NULL,
348             (atype_in_code == GB_UDT_code) ? sizeof(GxB_FC64_t) :atype_in->size,
349             anz, 1) ;
350     }
351 
352     //--------------------------------------------------------------------------
353     // look for CSR/CSC and hyper/non-hyper format
354     //--------------------------------------------------------------------------
355 
356     bool A_is_hyper = false ;
357     bool has_hyper_switch = false ;
358     bool has_sparsity_control = false ;
359     int sparsity_control = GxB_AUTO_SPARSITY ;
360     double hyper_switch = GxB_HYPER_DEFAULT ;
361 
362     if (mxIsStruct (A_matlab))
363     {
364         // look for A.is_csc
365         int fieldnumber = mxGetFieldNumber (A_matlab, "is_csc") ;
366         if (fieldnumber >= 0)
367         {
368             is_csc = mxGetScalar (mxGetFieldByNumber (A_matlab,
369                 0, fieldnumber)) ;
370         }
371 
372         // look for A.is_hyper (ignored if hyper_switch present
373         // or if A is full)
374         fieldnumber = mxGetFieldNumber (A_matlab, "is_hyper") ;
375         if (fieldnumber >= 0)
376         {
377             A_is_hyper = mxGetScalar (mxGetFieldByNumber (A_matlab,
378                 0, fieldnumber)) ;
379         }
380 
381         // look for A.hyper_switch (ignored if A is full)
382         fieldnumber = mxGetFieldNumber (A_matlab, "hyper_switch") ;
383         if (fieldnumber >= 0)
384         {
385             has_hyper_switch = true ;
386             hyper_switch = mxGetScalar (mxGetFieldByNumber (A_matlab,
387                 0, fieldnumber)) ;
388         }
389 
390         // look for A.sparsity
391         fieldnumber = mxGetFieldNumber (A_matlab, "sparsity") ;
392         if (fieldnumber >= 0)
393         {
394             has_sparsity_control = true ;
395             sparsity_control = mxGetScalar (mxGetFieldByNumber (A_matlab,
396                 0, fieldnumber)) ;
397         }
398     }
399 
400     //--------------------------------------------------------------------------
401     // compute the # of non-empty vectors in A only when needed
402     //--------------------------------------------------------------------------
403 
404     if (sparsity != GxB_FULL)
405     {
406         A->nvec_nonempty = -1 ;
407     }
408 
409     ASSERT_MATRIX_OK (A, "got natural A from MATLAB", GB0) ;
410     ASSERT (A->h == NULL) ;
411 
412     //--------------------------------------------------------------------------
413     // convert to CSR if requested
414     //--------------------------------------------------------------------------
415 
416     int64_t nrows_old = GB_NROWS (A) ;
417     int64_t ncols_old = GB_NCOLS (A) ;
418 
419     if (!is_csc)
420     {
421         // this might convert A to hypersparse
422         GxB_Matrix_Option_set_(A, GxB_FORMAT, GxB_BY_ROW) ;
423         // so convert it back; hypersparsity is defined below
424         if (sparsity != GxB_FULL)
425         {
426             GB_convert_hyper_to_sparse (A, Context) ;
427         }
428         ASSERT (!A->is_csc) ;
429     }
430 
431     ASSERT_MATRIX_OK (A, "conformed from MATLAB", GB0) ;
432     ASSERT (A->h == NULL) ;
433     ASSERT (A->is_csc == is_csc) ;
434 
435     //--------------------------------------------------------------------------
436     // convert to hypersparse or set hypersparse ratio, if requested
437     //--------------------------------------------------------------------------
438 
439     if (sparsity == GxB_FULL)
440     {
441         // leave as-is
442         ;
443     }
444     else if (has_hyper_switch)
445     {
446         // this sets the hyper_switch and then conforms the matrix to its
447         // desired hypersparsity.  It may stay non-hypersparse.
448         GxB_Matrix_Option_set_(A, GxB_HYPER_SWITCH, hyper_switch) ;
449     }
450     else if (A_is_hyper)
451     {
452         // this forces the matrix to be always hypersparse
453         ASSERT_MATRIX_OK (A, "to always hyper", GB0) ;
454         GxB_Matrix_Option_set_(A, GxB_SPARSITY_CONTROL, GxB_HYPERSPARSE) ;
455         ASSERT_MATRIX_OK (A, "always hyper", GB0) ;
456     }
457 
458     //--------------------------------------------------------------------------
459     // set the sparsity control and conform the matrix
460     //--------------------------------------------------------------------------
461 
462     if (has_sparsity_control)
463     {
464         ASSERT_MATRIX_OK (A, "setting sparsity", GB0) ;
465         GxB_Matrix_Option_set_(A, GxB_SPARSITY_CONTROL, sparsity_control) ;
466         ASSERT_MATRIX_OK (A, "set sparsity", GB0) ;
467     }
468 
469     ASSERT (A->is_csc == is_csc) ;
470     ASSERT (nrows_old == GB_NROWS (A)) ;
471     ASSERT (ncols_old == GB_NCOLS (A)) ;
472 
473     //--------------------------------------------------------------------------
474     // return the GraphBLAS matrix
475     //--------------------------------------------------------------------------
476 
477     info = GrB_Matrix_wait (&A) ;
478     if (info != GrB_SUCCESS)
479     {
480         FREE_ALL ;
481         mexWarnMsgIdAndTxt ("GB:warn", "matrix wait failed") ;
482         return (NULL) ;
483     }
484 
485     ASSERT_MATRIX_OK (A, "got A from MATLAB", GB0) ;
486     return (A) ;
487 }
488 
489