File: GB_setElement.c

package info (click to toggle)
suitesparse-graphblas 7.4.0%2Bdfsg-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 67,112 kB
  • sloc: ansic: 1,072,243; cpp: 8,081; sh: 512; makefile: 506; asm: 369; python: 125; awk: 10
file content (464 lines) | stat: -rw-r--r-- 16,432 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
//------------------------------------------------------------------------------
// GB_setElement: C(row,col) = scalar or += scalar
//------------------------------------------------------------------------------

// SuiteSparse:GraphBLAS, Timothy A. Davis, (c) 2017-2022, All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

//------------------------------------------------------------------------------

// Sets the value of single scalar, C(row,col) = scalar, or C(row,col)+=scalar,
// typecasting from the type of scalar to the type of C, as needed.  Not
// user-callable; does the work for all GrB_*_setElement* functions, and for
// GrB_*assign when a single entry is modified.

// If C(row,col) is already present in the matrix, its value is overwritten
// with the scalar.  Otherwise, if the mode determined by GrB_init is
// non-blocking, the tuple (i,j,scalar) is appended to a list of pending tuples
// to C.  GB_wait assembles these pending tuples.

// GB_setElement when accum is NULL is used by GrB_*_setElement.  It is the
// same as GrB_*assign with an implied SECOND accum operator whose ztype,
// xtype, and ytype are the same as C, with I=i, J=j, a 1-by-1 dense matrix A
// (where nnz (A) == 1), no mask, mask not complemented, C_replace effectively
// false (its value is ignored), and A transpose effectively false (since
// transposing a scalar has no effect).

// GB_setElement when accum is not NULL uses the accum operator instead of the
// implied SECOND operator.  It is used by GrB_*_assign, as a special case.

// Compare this function with GrB_*_extractElement_*

#include "GB_Pending.h"

#define GB_FREE_ALL ;

GrB_Info GB_setElement              // set a single entry, C(row,col) = scalar
(
    GrB_Matrix C,                   // matrix to modify
    const GrB_BinaryOp accum,       // if NULL: C(row,col) = scalar
                                    // else: C(row,col) += scalar
    const void *scalar,             // scalar to set
    const GrB_Index row,            // row index
    const GrB_Index col,            // column index
    const GB_Type_code scalar_code, // type of the scalar
    GB_Context Context
)
{

    //--------------------------------------------------------------------------
    // check inputs
    //--------------------------------------------------------------------------

    GrB_Info info ;
    ASSERT (C != NULL) ;
    GB_RETURN_IF_NULL (scalar) ;

    if (row >= GB_NROWS (C))
    { 
        GB_ERROR (GrB_INVALID_INDEX,
            "Row index " GBu " out of range; must be < " GBd,
            row, GB_NROWS (C)) ;
    }
    if (col >= GB_NCOLS (C))
    { 
        GB_ERROR (GrB_INVALID_INDEX,
            "Column index " GBu " out of range; must be < " GBd,
            col, GB_NCOLS (C)) ;
    }

    ASSERT (scalar_code <= GB_UDT_code) ;

    GrB_Type ctype = C->type ;
    GB_Type_code ccode = ctype->code ;

    // scalar_code and C must be compatible
    if (!GB_code_compatible (scalar_code, ccode))
    { 
        GB_ERROR (GrB_DOMAIN_MISMATCH,
            "Input scalar of type [%s]\n"
            "cannot be typecast to entry of type [%s]",
            GB_code_string (scalar_code), ctype->name) ;
    }

    if (accum != NULL)
    { 
        // C and scalar must be compatible with the accum operator
        GB_RETURN_IF_FAULTY_OR_POSITIONAL (accum) ;
        GB_OK (GB_BinaryOp_compatible (accum, ctype, ctype, NULL, scalar_code,
            Context)) ;
    }

    // pending tuples and zombies are expected, and C might be jumbled too
    ASSERT (GB_JUMBLED_OK (C)) ;
    ASSERT (GB_PENDING_OK (C)) ;
    ASSERT (GB_ZOMBIES_OK (C)) ;

    //--------------------------------------------------------------------------
    // sort C if needed; do not assemble pending tuples or kill zombies yet
    //--------------------------------------------------------------------------

    if (C->jumbled)
    { 
        GB_OK (GB_wait (C, "C (setElement:jumbled)", Context)) ;
    }

    // zombies and pending tuples are still OK, but C is no longer jumbled
    ASSERT (!GB_JUMBLED (C)) ;
    ASSERT (GB_PENDING_OK (C)) ;
    ASSERT (GB_ZOMBIES_OK (C)) ;

    bool C_is_full = GB_IS_FULL (C) ;

    //--------------------------------------------------------------------------
    // check if C needs to convert to non-iso, or if C is a new iso matrix
    //--------------------------------------------------------------------------

    // stype is the type of this scalar
    GrB_Type stype = GB_code_type (scalar_code, ctype) ;
    size_t csize = ctype->size ;

    if (C->iso)
    {

        //----------------------------------------------------------------------
        // typecast the scalar and compare with the iso value of C
        //----------------------------------------------------------------------

        bool convert_to_non_iso ;
        if (accum != NULL)
        { 
            // C(i,j) += scalar always converts C to non-iso
            convert_to_non_iso = true ;
        }
        else if (ctype != stype)
        { 
            // s = (ctype) scalar
            GB_void s [GB_VLA(csize)] ;
            GB_cast_scalar (s, ccode, scalar, scalar_code, csize) ;
            // compare s with the iso value of C
            convert_to_non_iso = (memcmp (C->x, s, csize) != 0) ;
        }
        else
        { 
            // compare the scalar with the iso value of C
            convert_to_non_iso = (memcmp (C->x, scalar, csize) != 0) ;
        }

        if (convert_to_non_iso)
        { 
            // The new entry differs from the iso value of C.  Assemble all
            // pending tuples and convert C to non-iso.  Zombies are OK.
            if (C->Pending != NULL)
            { 
                GB_OK (GB_wait (C, "C (setElement:to non-iso)", Context)) ;
            }
            GB_OK (GB_convert_any_to_non_iso (C, true, Context)) ;
        }

    }
    else if (GB_nnz (C) == 0 && !C_is_full && C->Pending == NULL
        && accum == NULL)
    {

        //----------------------------------------------------------------------
        // C is empty: this is the first setElement, convert C to iso
        //----------------------------------------------------------------------

        if (ctype != stype)
        { 
            // s = (ctype) scalar
            GB_void s [GB_VLA(csize)] ;
            GB_cast_scalar (s, ccode, scalar, scalar_code, csize) ;
            GB_OK (GB_convert_any_to_iso (C, s, Context)) ;
        }
        else
        { 
            GB_OK (GB_convert_any_to_iso (C, (GB_void *) scalar, Context)) ;
        }
    }

    //--------------------------------------------------------------------------
    // handle the CSR/CSC format
    //--------------------------------------------------------------------------

    int64_t i, j ;
    if (C->is_csc)
    { 
        // set entry with index i in vector j
        i = row ;
        j = col ;
    }
    else
    { 
        // set entry with index j in vector i
        i = col ;
        j = row ;
    }

    int64_t pleft ;
    bool found = false ;
    bool is_zombie ;
    bool C_is_bitmap = GB_IS_BITMAP (C) ;
    C_is_full = GB_IS_FULL (C) ;

    if (C_is_full || C_is_bitmap)
    { 

        //----------------------------------------------------------------------
        // C is bitmap or full
        //----------------------------------------------------------------------

        pleft = i + j * C->vlen ;
        found = true ;
        is_zombie = false ;

    }
    else
    {

        //----------------------------------------------------------------------
        // C is sparse or hypersparse
        //----------------------------------------------------------------------

        int64_t pC_start, pC_end ;
        const int64_t *restrict Ch = C->h ;
        if (C->nvals == 0)
        { 
            // C is empty
            found = false ;
        }
        else if (Ch != NULL)
        {
            // C is hypersparse, with at least one entry
            int64_t k ;
            if (C->Y == NULL)
            { 
                // C is hypersparse but does not yet have a hyper_hash
                k = 0 ;
                found = GB_lookup (true, Ch, C->p, C->vlen, &k,
                    C->nvec-1, j, &pC_start, &pC_end) ;
            }
            else
            { 
                // C is hypersparse, with a hyper_hash that is already built
                k = GB_hyper_hash_lookup (C->p, C->Y->p, C->Y->i, C->Y->x,
                    C->Y->vdim-1, j, &pC_start, &pC_end) ;
                found = (k >= 0) ;
            }
            ASSERT (GB_IMPLIES (found, j == Ch [k])) ;
        }
        else
        { 
            // C is sparse
            pC_start = C->p [j] ;
            pC_end   = C->p [j+1] ;
            found = true ;
        }

        //----------------------------------------------------------------------
        // binary search in kth vector for index i
        //----------------------------------------------------------------------

        if (found)
        { 
            // vector j has been found; now look for index i
            pleft = pC_start ;
            int64_t pright = pC_end - 1 ;

            // Time taken for this step is at most O(log(nnz(C(:,j))).
            const int64_t *restrict Ci = C->i ;
            GB_BINARY_SEARCH_ZOMBIE (i, Ci, pleft, pright, found,
                C->nzombies, is_zombie) ;
        }
    }

    //--------------------------------------------------------------------------
    // set the element
    //--------------------------------------------------------------------------

    if (found)
    {

        //----------------------------------------------------------------------
        // C (i,j) found
        //----------------------------------------------------------------------

        // if not zombie:
        //      no accum:   action: ( =A ): copy A into C
        //      with accum: action: ( C+=A ): accumulate A into C
        // else             action: ( undelete ): bring a zombie back to life

        int8_t cb = (C_is_bitmap) ? C->b [pleft] : 0 ;

        if (!C->iso)
        { 
            void *cx = ((GB_void *) C->x) + (pleft*csize) ;
            if (accum == NULL || is_zombie || (C_is_bitmap && cb == 0))
            { 
                // C(i,j) = (ctype) scalar
                GB_cast_scalar (cx, ccode, scalar, scalar_code, csize) ;
            }
            else
            { 
                // C(i,j) += scalar
                GxB_binary_function faccum = accum->binop_function ;

                GB_cast_function cast_C_to_X, cast_Z_to_Y, cast_Z_to_C ;
                cast_C_to_X = GB_cast_factory (accum->xtype->code, ctype->code);
                cast_Z_to_Y = GB_cast_factory (accum->ytype->code, scalar_code);
                cast_Z_to_C = GB_cast_factory (ctype->code, accum->ztype->code);

                // scalar workspace
                GB_void xaccum [GB_VLA(accum->xtype->size)] ;
                GB_void yaccum [GB_VLA(accum->ytype->size)] ;
                GB_void zaccum [GB_VLA(accum->ztype->size)] ;

                // xaccum = (accum->xtype) cx
                cast_C_to_X (xaccum, cx, ctype->size) ;

                // yaccum = (accum->ytype) scalar
                cast_Z_to_Y (yaccum, scalar, accum->ytype->size) ;

                // zaccum = xaccum "+" yaccum
                faccum (zaccum, xaccum, yaccum) ;

                // cx = (ctype) zaccum
                cast_Z_to_C (cx, zaccum, ctype->size) ;
            }
        }

        if (is_zombie)
        { 
            // bring the zombie back to life
            C->i [pleft] = i ;
            C->nzombies-- ;
        }
        else if (C_is_bitmap)
        { 
            // set the entry in the C bitmap
            C->nvals += (cb == 0) ;
            C->b [pleft] = 1 ;
        }

        return (GrB_SUCCESS) ;

    }
    else
    {

        //----------------------------------------------------------------------
        // C (i,j) not found: add a pending tuple
        //----------------------------------------------------------------------

        // action: ( insert )

        // No typecasting can be done.  The new pending tuple must either be
        // the first pending tuple, or its type must match the prior pending
        // tuples.  See GB_subassign_methods.h for a complete description.

        //----------------------------------------------------------------------
        // check for wait
        //----------------------------------------------------------------------

        bool wait = false ;

        if (C->Pending == NULL)
        { 
            // the new pending tuple is the first one, so it will define
            // C->type-pending = stype.  No need to wait.
            wait = false ;
        }
        else
        {
            if (stype != C->Pending->type)
            { 
                // the scalar type (stype) must match the type of the
                // prior pending tuples.  If the type is different, prior
                // pending tuples must be assembled first.
                wait = true ;
            }
            else if
            (
                // the types match, now check the pending operator
                ! (
                    // the operators are the same
                    (accum == C->Pending->op)
                    // or both operators are SECOND_Ctype, implicit or explicit
                    || (GB_op_is_second (accum, ctype) &&
                        GB_op_is_second (C->Pending->op, ctype))
                  )
            )
            { 
                wait = true ;
            }
        }

        if (wait)
        { 

            //------------------------------------------------------------------
            // incompatible pending tuples: wait is required
            //------------------------------------------------------------------

            // Pending tuples exist.  Either the pending operator is not
            // SECOND_ctype (implicit or explicit), or the type of prior
            // pending tuples is not the same as the type of the scalar.  This
            // new tuple requires both conditions to hold.  All prior tuples
            // must be assembled before this new one can be added.
            GB_OK (GB_wait (C, "C (setElement:incompatible pending tuples)",
                Context)) ;

            // repeat the search since the C(i,j) entry may have been in
            // the list of pending tuples.  There are no longer any pending
            // tuples, so this recursion will only happen once.  The
            // pending operator will become the implicit SECOND_ctype, or
            // accum, and the type of the pending tuples will become stype.
            return (GB_setElement (C, accum, scalar, row, col, scalar_code,
                Context)) ;

        }
        else
        {

            //------------------------------------------------------------------
            // the new tuple is now compatible with prior tuples, if any
            //------------------------------------------------------------------

            ASSERT (GB_PENDING_OK (C)) ;
            ASSERT (GB_ZOMBIES_OK (C)) ;

            // C (i,j) must be added to the list of pending tuples.
            // If this is the first pending tuple, then the type of pending
            // tuples becomes the type of this scalar, and the pending operator
            // becomes NULL, which is the implicit SECOND_ctype operator,
            // or non-NULL if accum is present.
            if (!GB_Pending_add (&(C->Pending), C->iso, (GB_void *) scalar,
                stype, accum, i, j, C->vdim > 1, Context))
            { 
                // out of memory
                GB_phybix_free (C) ;
                return (GrB_OUT_OF_MEMORY) ;
            }

            ASSERT (GB_PENDING (C)) ;

            // if this was the first tuple, then the pending operator and
            // pending type have been defined
            if (accum == NULL)
            {
                ASSERT (GB_op_is_second (C->Pending->op, ctype)) ;
            }
            else
            {
                ASSERT (C->Pending->op == accum) ;
            }
            ASSERT (C->Pending->type == stype) ;
            ASSERT (C->Pending->size == stype->size) ;

            // one more pending tuple; block if too many of them
            return (GB_block (C, Context)) ;
        }
    }
}