1function Cout = GB_spec_matrix (Cin, identity)
2%GB_SPEC_MATRIX a MATLAB mimic that conforms a matrix to the GraphBLAS spec
3%
4% Cout = GB_spec_matrix (Cin)
5% Cout = GB_spec_matrix (Cin, identity)
6%
7% Conforms an input matrix to the GraphBLAS spec, for use in GB_spec_* only.
8%
9% The identity argument is the addititive identity of the semiring that will be
10% used on the matrix (default is zero).  It represents the value of the
11% implicit 'zeros' that are not present in a sparse matrix.
12%
13% The input Cin is either a matrix or a struct.
14%
15% If Cin is a struct, it must have a field Cin.matrix, and it can have two
16% optional fields, Cin.pattern and Cin.class.  Let X = Cin.matrix.  The type
17% of X is given by Cin.class, if present, or type(X) otherwise.  For a dense
18% X, type(X) and Cin.class, if present, must match.  If present, the pattern
19% of X is given by Cin.pattern; otherwise the pattern for a sparse X is
20% GB_spones_mex(X) and entries outside the pattern are assumed to be equal to
21% identity.  For a dense X, with no Cin.pattern present the pattern of X is
22% all true.
23%
24% If Cin is a matrix, then its type is given by GB_spec_type (Cin).  If the
25% matrix is sparse, its pattern is GB_spones_mex(Cin) and entries not in the
26% pattern are assumed equal to identity.  Otherwise the pattern of Cin is
27% all true.
28%
29% The output Cout is a struct with all three fields present (matrix, pattern,
30% and type).  Cout.matrix is dense, and it has been typecast into the type
31% given by Cout.class.  Entries not in the pattern of X are explicitly set to
32% identity.
33%
34% The matrix is typecast to Cout.class using the typecasting GraphBLAS
35% typecasting rules, which differ from MATLAB's rules, particular for integer
36% types.  Since MATLAB can only represent 'logical' and 'double' sparse
37% matries, the matrix is converted to full.
38%
39% Converting a matrix from sparse to full is not part of the GraphBLAS spec, of
40% course.  Neither is the identity value a part of the matrix structure in
41% GraphBLAS.  A matrix in GraphBLAS is always sparse, and entries not in the
42% pattern are always implicitly equal to whatever semiring is used on the
43% sparse matrix.  As a result, GraphBLAS never needs to know what the identity
44% value is until it operates on the matrix, and the identity is not part of the
45% matrix itself.  Using a different semiring immediately changes the value of
46% the implicit zero, with a change to the GraphBLAS data structure for the
47% matrix.
48%
49% However, MATLAB only supports logical, double, and double complex
50% sparse matrices, so to the MATLAB mimic functions GB_spec_* operate only on
51% dense matrices.  When a MATLAB sparse matrix is converted into a dense matrix,
52% the entries not in the pattern must be set to an explicit value: the
53% addititive identity.
54%
55% The MATLAB mimic routines, GB_spec_* are written almost purely in M, and
56% do not call GraphBLAS functions (with the exception of typecasting and
57% operators).  They always return a stuct C with a dense C.matrix, a
58% pattern C.pattern and type C.class.
59%
60% GraphBLAS GB_mex_* functions are direct interfaces to the C functions in
61% GraphBLAS, and they always return a sparse struct; otherwise large problems
62% could not be solved.  The struct contains just C.matrix and C.class.  The
63% pattern of C.matrix is GB_spones_mex(C.matrix).  To compare the output C0 of
64% a GB_mex_* function with C1 of a GrapBLAS_spec_* function, the struct C0
65% must first be passed to this function, C0=GB_spec_matrix(C0,identity) and
66% then C0 and C1 should be identical.
67
68% SuiteSparse:GraphBLAS, Timothy A. Davis, (c) 2017-2021, All Rights Reserved.
69% SPDX-License-Identifier: Apache-2.0
70
71% get the semiring addititive identity, if present
72if (nargin < 2)
73    identity = 0 ;      % default is zero
74end
75
76% get the matrix
77if (isstruct (Cin))
78    X = Cin.matrix ;
79else
80    X = Cin ;
81end
82
83% get the type
84if (isstruct (Cin))
85    if (isfield (Cin, 'class'))
86        % get the type of X from Cin.class
87        xtype = Cin.class ;
88        if (~issparse (X) && ~isequal (xtype, GB_spec_type (X)))
89            % for a dense X, GB_spec_type (X) and Cin.class must match
90            X = GB_mex_cast (X, xtype) ;
91        end
92    else
93        % no Cin.class present, so get the type from X itself
94        xtype = GB_spec_type (X) ;
95    end
96else
97    % Cin is a matrix, so get the type from X itself
98    xtype = GB_spec_type (X) ;
99end
100
101% get the pattern
102if (isstruct (Cin) && isfield (Cin, 'pattern'))
103    xpattern = Cin.pattern ;
104else
105    % must be done before typecasting since it can introduce zeros
106    if (issparse (X))
107        % For a sparse matrix, use the actual pattern.  Entries not in the
108        % the pattern are assumed to be equal to the addititve identity.
109        xpattern = GB_mex_cast (full (GB_spones_mex (X)), 'logical') ;
110    else
111        xpattern = true (size (X)) ;
112        % xpattern = (X ~= identity) ;
113    end
114end
115
116% get the uncasted values, if present
117if (isstruct (Cin) && isfield (Cin, 'values') &&  ...
118    (isequal (xtype, 'int64') || isequal (xtype, 'uint64')))
119    % Cin.matrix is sparse, either double or logical.  But it was typecasted
120    % from a GraphBLAS matrix that was not double or logical.  Typecasting
121    % to int64 or uint64 can lead to loss of precision.  So in this case,
122    % use Cin.values instead.
123    X = GB_spec_zeros (size (Cin.matrix), xtype) ;
124    [I J ~] = find (xpattern) ;
125    Cx = Cin.values ;
126    assert (length (I) == length (Cx)) ;
127    for k = 1:length (Cin.values)
128        X (I (k), J (k)) = Cx (k) ;
129    end
130else
131    % typecast X to the requested type and make it dense
132    X = GB_mex_cast (full (X), xtype) ;
133end
134
135% in the dense X, entries not in the xpattern must be set to the identity
136X (~xpattern) = GB_mex_cast (identity, xtype) ;
137
138if (~isequal (xtype, GB_spec_type (X)))
139    % if X is complex, it may have been downgraded to real, if
140    % its imaginary part is zero
141    X = GB_mex_cast (X, xtype) ;
142end
143
144% return the output struct
145Cout.matrix = X ;
146Cout.pattern = xpattern ;
147Cout.class = xtype ;
148
149% The output is now a struct with all 3 fields present, and Cout.matrix
150% is always dense.  Cout.class always matches type(Cout.matrix).
151assert (isstruct (Cout)) ;
152assert (~issparse (Cout.matrix)) ;
153
154