1 /*$Id: matstash.c,v 1.50 2001/03/23 23:22:45 balay Exp $*/ 2 3 #include "src/mat/matimpl.h" 4 5 /* 6 The input to the stash is ALWAYS in MatScalar precision, and the 7 internal storage and output is also in MatScalar. 8 */ 9 #define DEFAULT_STASH_SIZE 10000 10 11 /* 12 MatStashCreate_Private - Creates a stash,currently used for all the parallel 13 matrix implementations. The stash is where elements of a matrix destined 14 to be stored on other processors are kept until matrix assembly is done. 15 16 This is a simple minded stash. Simply adds entries to end of stash. 17 18 Input Parameters: 19 comm - communicator, required for scatters. 20 bs - stash block size. used when stashing blocks of values 21 22 Output Parameters: 23 stash - the newly created stash 24 */ 25 #undef __FUNCT__ 26 #define __FUNCT__ "MatStashCreate_Private" 27 int MatStashCreate_Private(MPI_Comm comm,int bs,MatStash *stash) 28 { 29 int ierr,max,*opt,nopt; 30 PetscTruth flg; 31 32 PetscFunctionBegin; 33 /* Require 2 tags,get the second using PetscCommGetNewTag() */ 34 stash->comm = comm; 35 ierr = PetscCommGetNewTag(stash->comm,&stash->tag1);CHKERRQ(ierr); 36 ierr = PetscCommGetNewTag(stash->comm,&stash->tag2);CHKERRQ(ierr); 37 ierr = MPI_Comm_size(stash->comm,&stash->size);CHKERRQ(ierr); 38 ierr = MPI_Comm_rank(stash->comm,&stash->rank);CHKERRQ(ierr); 39 40 nopt = stash->size; 41 ierr = PetscMalloc(nopt*sizeof(int),&opt);CHKERRQ(ierr); 42 ierr = PetscOptionsGetIntArray(PETSC_NULL,"-matstash_initial_size",opt,&nopt,&flg);CHKERRQ(ierr); 43 if (flg) { 44 if (nopt == 1) max = opt[0]; 45 else if (nopt == stash->size) max = opt[stash->rank]; 46 else if (stash->rank < nopt) max = opt[stash->rank]; 47 else max = 0; /* Use default */ 48 stash->umax = max; 49 } else { 50 stash->umax = 0; 51 } 52 ierr = PetscFree(opt);CHKERRQ(ierr); 53 if (bs <= 0) bs = 1; 54 55 stash->bs = bs; 56 stash->nmax = 0; 57 stash->oldnmax = 0; 58 stash->n = 0; 59 stash->reallocs = -1; 60 stash->idx = 0; 61 stash->idy = 0; 62 stash->array = 0; 63 64 stash->send_waits = 0; 65 stash->recv_waits = 0; 66 stash->send_status = 0; 67 stash->nsends = 0; 68 stash->nrecvs = 0; 69 stash->svalues = 0; 70 stash->rvalues = 0; 71 stash->rmax = 0; 72 stash->nprocs = 0; 73 stash->nprocessed = 0; 74 PetscFunctionReturn(0); 75 } 76 77 /* 78 MatStashDestroy_Private - Destroy the stash 79 */ 80 #undef __FUNCT__ 81 #define __FUNCT__ "MatStashDestroy_Private" 82 int MatStashDestroy_Private(MatStash *stash) 83 { 84 int ierr; 85 86 PetscFunctionBegin; 87 if (stash->array) { 88 ierr = PetscFree(stash->array);CHKERRQ(ierr); 89 stash->array = 0; 90 } 91 PetscFunctionReturn(0); 92 } 93 94 /* 95 MatStashScatterEnd_Private - This is called as the fial stage of 96 scatter. The final stages of messagepassing is done here, and 97 all the memory used for messagepassing is cleanedu up. This 98 routine also resets the stash, and deallocates the memory used 99 for the stash. It also keeps track of the current memory usage 100 so that the same value can be used the next time through. 101 */ 102 #undef __FUNCT__ 103 #define __FUNCT__ "MatStashScatterEnd_Private" 104 int MatStashScatterEnd_Private(MatStash *stash) 105 { 106 int nsends=stash->nsends,ierr,bs2,oldnmax; 107 MPI_Status *send_status; 108 109 PetscFunctionBegin; 110 /* wait on sends */ 111 if (nsends) { 112 ierr = PetscMalloc(2*nsends*sizeof(MPI_Status),&send_status);CHKERRQ(ierr); 113 ierr = MPI_Waitall(2*nsends,stash->send_waits,send_status);CHKERRQ(ierr); 114 ierr = PetscFree(send_status);CHKERRQ(ierr); 115 } 116 117 /* Now update nmaxold to be app 10% more than max n used, this way the 118 wastage of space is reduced the next time this stash is used. 119 Also update the oldmax, only if it increases */ 120 if (stash->n) { 121 bs2 = stash->bs*stash->bs; 122 oldnmax = ((int)(stash->n * 1.1) + 5)*bs2; 123 if (oldnmax > stash->oldnmax) stash->oldnmax = oldnmax; 124 } 125 126 stash->nmax = 0; 127 stash->n = 0; 128 stash->reallocs = -1; 129 stash->rmax = 0; 130 stash->nprocessed = 0; 131 132 if (stash->array) { 133 ierr = PetscFree(stash->array);CHKERRQ(ierr); 134 stash->array = 0; 135 stash->idx = 0; 136 stash->idy = 0; 137 } 138 if (stash->send_waits) { 139 ierr = PetscFree(stash->send_waits);CHKERRQ(ierr); 140 stash->send_waits = 0; 141 } 142 if (stash->recv_waits) { 143 ierr = PetscFree(stash->recv_waits);CHKERRQ(ierr); 144 stash->recv_waits = 0; 145 } 146 if (stash->svalues) { 147 ierr = PetscFree(stash->svalues);CHKERRQ(ierr); 148 stash->svalues = 0; 149 } 150 if (stash->rvalues) { 151 ierr = PetscFree(stash->rvalues);CHKERRQ(ierr); 152 stash->rvalues = 0; 153 } 154 if (stash->nprocs) { 155 ierr = PetscFree(stash->nprocs);CHKERRQ(ierr); 156 stash->nprocs = 0; 157 } 158 159 PetscFunctionReturn(0); 160 } 161 162 /* 163 MatStashGetInfo_Private - Gets the relavant statistics of the stash 164 165 Input Parameters: 166 stash - the stash 167 nstash - the size of the stash. Indicates the number of values stored. 168 reallocs - the number of additional mallocs incurred. 169 170 */ 171 #undef __FUNCT__ 172 #define __FUNCT__ "MatStashGetInfo_Private" 173 int MatStashGetInfo_Private(MatStash *stash,int *nstash,int *reallocs) 174 { 175 int bs2 = stash->bs*stash->bs; 176 177 PetscFunctionBegin; 178 if (nstash) *nstash = stash->n*bs2; 179 if (reallocs) { 180 if (stash->reallocs < 0) *reallocs = 0; 181 else *reallocs = stash->reallocs; 182 } 183 PetscFunctionReturn(0); 184 } 185 186 187 /* 188 MatStashSetInitialSize_Private - Sets the initial size of the stash 189 190 Input Parameters: 191 stash - the stash 192 max - the value that is used as the max size of the stash. 193 this value is used while allocating memory. 194 */ 195 #undef __FUNCT__ 196 #define __FUNCT__ "MatStashSetInitialSize_Private" 197 int MatStashSetInitialSize_Private(MatStash *stash,int max) 198 { 199 PetscFunctionBegin; 200 stash->umax = max; 201 PetscFunctionReturn(0); 202 } 203 204 /* MatStashExpand_Private - Expand the stash. This function is called 205 when the space in the stash is not sufficient to add the new values 206 being inserted into the stash. 207 208 Input Parameters: 209 stash - the stash 210 incr - the minimum increase requested 211 212 Notes: 213 This routine doubles the currently used memory. 214 */ 215 #undef __FUNCT__ 216 #define __FUNCT__ "MatStashExpand_Private" 217 static int MatStashExpand_Private(MatStash *stash,int incr) 218 { 219 int *n_idx,*n_idy,newnmax,bs2,ierr; 220 MatScalar *n_array; 221 222 PetscFunctionBegin; 223 /* allocate a larger stash */ 224 bs2 = stash->bs*stash->bs; 225 if (!stash->oldnmax && !stash->nmax) { /* new stash */ 226 if (stash->umax) newnmax = stash->umax/bs2; 227 else newnmax = DEFAULT_STASH_SIZE/bs2; 228 } else if (!stash->nmax) { /* resuing stash */ 229 if (stash->umax > stash->oldnmax) newnmax = stash->umax/bs2; 230 else newnmax = stash->oldnmax/bs2; 231 } else newnmax = stash->nmax*2; 232 if (newnmax < (stash->nmax + incr)) newnmax += 2*incr; 233 234 ierr = PetscMalloc((newnmax)*(2*sizeof(int)+bs2*sizeof(MatScalar)),&n_array);CHKERRQ(ierr); 235 n_idx = (int*)(n_array + bs2*newnmax); 236 n_idy = (int*)(n_idx + newnmax); 237 ierr = PetscMemcpy(n_array,stash->array,bs2*stash->nmax*sizeof(MatScalar));CHKERRQ(ierr); 238 ierr = PetscMemcpy(n_idx,stash->idx,stash->nmax*sizeof(int));CHKERRQ(ierr); 239 ierr = PetscMemcpy(n_idy,stash->idy,stash->nmax*sizeof(int));CHKERRQ(ierr); 240 if (stash->array) {ierr = PetscFree(stash->array);CHKERRQ(ierr);} 241 stash->array = n_array; 242 stash->idx = n_idx; 243 stash->idy = n_idy; 244 stash->nmax = newnmax; 245 stash->reallocs++; 246 PetscFunctionReturn(0); 247 } 248 /* 249 MatStashValuesRow_Private - inserts values into the stash. This function 250 expects the values to be roworiented. Multiple columns belong to the same row 251 can be inserted with a single call to this function. 252 253 Input Parameters: 254 stash - the stash 255 row - the global row correspoiding to the values 256 n - the number of elements inserted. All elements belong to the above row. 257 idxn - the global column indices corresponding to each of the values. 258 values - the values inserted 259 */ 260 #undef __FUNCT__ 261 #define __FUNCT__ "MatStashValuesRow_Private" 262 int MatStashValuesRow_Private(MatStash *stash,int row,int n,const int idxn[],const MatScalar values[]) 263 { 264 int ierr,i; 265 266 PetscFunctionBegin; 267 /* Check and see if we have sufficient memory */ 268 if ((stash->n + n) > stash->nmax) { 269 ierr = MatStashExpand_Private(stash,n);CHKERRQ(ierr); 270 } 271 for (i=0; i<n; i++) { 272 stash->idx[stash->n] = row; 273 stash->idy[stash->n] = idxn[i]; 274 stash->array[stash->n] = values[i]; 275 stash->n++; 276 } 277 PetscFunctionReturn(0); 278 } 279 /* 280 MatStashValuesCol_Private - inserts values into the stash. This function 281 expects the values to be columnoriented. Multiple columns belong to the same row 282 can be inserted with a single call to this function. 283 284 Input Parameters: 285 stash - the stash 286 row - the global row correspoiding to the values 287 n - the number of elements inserted. All elements belong to the above row. 288 idxn - the global column indices corresponding to each of the values. 289 values - the values inserted 290 stepval - the consecutive values are sepated by a distance of stepval. 291 this happens because the input is columnoriented. 292 */ 293 #undef __FUNCT__ 294 #define __FUNCT__ "MatStashValuesCol_Private" 295 int MatStashValuesCol_Private(MatStash *stash,int row,int n,const int idxn[],const MatScalar values[],int stepval) 296 { 297 int ierr,i; 298 299 PetscFunctionBegin; 300 /* Check and see if we have sufficient memory */ 301 if ((stash->n + n) > stash->nmax) { 302 ierr = MatStashExpand_Private(stash,n);CHKERRQ(ierr); 303 } 304 for (i=0; i<n; i++) { 305 stash->idx[stash->n] = row; 306 stash->idy[stash->n] = idxn[i]; 307 stash->array[stash->n] = values[i*stepval]; 308 stash->n++; 309 } 310 PetscFunctionReturn(0); 311 } 312 313 /* 314 MatStashValuesRowBlocked_Private - inserts blocks of values into the stash. 315 This function expects the values to be roworiented. Multiple columns belong 316 to the same block-row can be inserted with a single call to this function. 317 This function extracts the sub-block of values based on the dimensions of 318 the original input block, and the row,col values corresponding to the blocks. 319 320 Input Parameters: 321 stash - the stash 322 row - the global block-row correspoiding to the values 323 n - the number of elements inserted. All elements belong to the above row. 324 idxn - the global block-column indices corresponding to each of the blocks of 325 values. Each block is of size bs*bs. 326 values - the values inserted 327 rmax - the number of block-rows in the original block. 328 cmax - the number of block-columsn on the original block. 329 idx - the index of the current block-row in the original block. 330 */ 331 #undef __FUNCT__ 332 #define __FUNCT__ "MatStashValuesRowBlocked_Private" 333 int MatStashValuesRowBlocked_Private(MatStash *stash,int row,int n,const int idxn[],const MatScalar values[],int rmax,int cmax,int idx) 334 { 335 int ierr,i,j,k,bs2,bs=stash->bs; 336 const MatScalar *vals; 337 MatScalar *array; 338 339 PetscFunctionBegin; 340 bs2 = bs*bs; 341 if ((stash->n+n) > stash->nmax) { 342 ierr = MatStashExpand_Private(stash,n);CHKERRQ(ierr); 343 } 344 for (i=0; i<n; i++) { 345 stash->idx[stash->n] = row; 346 stash->idy[stash->n] = idxn[i]; 347 /* Now copy over the block of values. Store the values column oriented. 348 This enables inserting multiple blocks belonging to a row with a single 349 funtion call */ 350 array = stash->array + bs2*stash->n; 351 vals = values + idx*bs2*n + bs*i; 352 for (j=0; j<bs; j++) { 353 for (k=0; k<bs; k++) {array[k*bs] = vals[k];} 354 array += 1; 355 vals += cmax*bs; 356 } 357 stash->n++; 358 } 359 PetscFunctionReturn(0); 360 } 361 362 /* 363 MatStashValuesColBlocked_Private - inserts blocks of values into the stash. 364 This function expects the values to be roworiented. Multiple columns belong 365 to the same block-row can be inserted with a single call to this function. 366 This function extracts the sub-block of values based on the dimensions of 367 the original input block, and the row,col values corresponding to the blocks. 368 369 Input Parameters: 370 stash - the stash 371 row - the global block-row correspoiding to the values 372 n - the number of elements inserted. All elements belong to the above row. 373 idxn - the global block-column indices corresponding to each of the blocks of 374 values. Each block is of size bs*bs. 375 values - the values inserted 376 rmax - the number of block-rows in the original block. 377 cmax - the number of block-columsn on the original block. 378 idx - the index of the current block-row in the original block. 379 */ 380 #undef __FUNCT__ 381 #define __FUNCT__ "MatStashValuesColBlocked_Private" 382 int MatStashValuesColBlocked_Private(MatStash *stash,int row,int n,const int idxn[],const MatScalar values[],int rmax,int cmax,int idx) 383 { 384 int ierr,i,j,k,bs2,bs=stash->bs; 385 const MatScalar *vals; 386 MatScalar *array; 387 388 PetscFunctionBegin; 389 bs2 = bs*bs; 390 if ((stash->n+n) > stash->nmax) { 391 ierr = MatStashExpand_Private(stash,n);CHKERRQ(ierr); 392 } 393 for (i=0; i<n; i++) { 394 stash->idx[stash->n] = row; 395 stash->idy[stash->n] = idxn[i]; 396 /* Now copy over the block of values. Store the values column oriented. 397 This enables inserting multiple blocks belonging to a row with a single 398 funtion call */ 399 array = stash->array + bs2*stash->n; 400 vals = values + idx*bs + bs2*rmax*i; 401 for (j=0; j<bs; j++) { 402 for (k=0; k<bs; k++) {array[k] = vals[k];} 403 array += bs; 404 vals += rmax*bs; 405 } 406 stash->n++; 407 } 408 PetscFunctionReturn(0); 409 } 410 /* 411 MatStashScatterBegin_Private - Initiates the transfer of values to the 412 correct owners. This function goes through the stash, and check the 413 owners of each stashed value, and sends the values off to the owner 414 processors. 415 416 Input Parameters: 417 stash - the stash 418 owners - an array of size 'no-of-procs' which gives the ownership range 419 for each node. 420 421 Notes: The 'owners' array in the cased of the blocked-stash has the 422 ranges specified blocked global indices, and for the regular stash in 423 the proper global indices. 424 */ 425 #undef __FUNCT__ 426 #define __FUNCT__ "MatStashScatterBegin_Private" 427 int MatStashScatterBegin_Private(MatStash *stash,int *owners) 428 { 429 int *owner,*startv,*starti,tag1=stash->tag1,tag2=stash->tag2,bs2; 430 int size=stash->size,*nprocs,nsends,nreceives; 431 int nmax,count,ierr,*sindices,*rindices,i,j,idx; 432 MatScalar *rvalues,*svalues; 433 MPI_Comm comm = stash->comm; 434 MPI_Request *send_waits,*recv_waits; 435 436 PetscFunctionBegin; 437 438 bs2 = stash->bs*stash->bs; 439 /* first count number of contributors to each processor */ 440 ierr = PetscMalloc(2*size*sizeof(int),&nprocs);CHKERRQ(ierr); 441 ierr = PetscMemzero(nprocs,2*size*sizeof(int));CHKERRQ(ierr); 442 ierr = PetscMalloc((stash->n+1)*sizeof(int),&owner);CHKERRQ(ierr); 443 444 for (i=0; i<stash->n; i++) { 445 idx = stash->idx[i]; 446 for (j=0; j<size; j++) { 447 if (idx >= owners[j] && idx < owners[j+1]) { 448 nprocs[2*j]++; nprocs[2*j+1] = 1; owner[i] = j; break; 449 } 450 } 451 } 452 nsends = 0; for (i=0; i<size; i++) { nsends += nprocs[2*i+1];} 453 454 /* inform other processors of number of messages and max length*/ 455 ierr = PetscMaxSum(comm,nprocs,&nmax,&nreceives);CHKERRQ(ierr); 456 457 /* post receives: 458 since we don't know how long each individual message is we 459 allocate the largest needed buffer for each receive. Potentially 460 this is a lot of wasted space. 461 */ 462 ierr = PetscMalloc((nreceives+1)*(nmax+1)*(bs2*sizeof(MatScalar)+2*sizeof(int)),&rvalues);CHKERRQ(ierr); 463 rindices = (int*)(rvalues + bs2*nreceives*nmax); 464 ierr = PetscMalloc((nreceives+1)*2*sizeof(MPI_Request),&recv_waits);CHKERRQ(ierr); 465 for (i=0,count=0; i<nreceives; i++) { 466 ierr = MPI_Irecv(rvalues+bs2*nmax*i,bs2*nmax,MPIU_MATSCALAR,MPI_ANY_SOURCE,tag1,comm, 467 recv_waits+count++);CHKERRQ(ierr); 468 ierr = MPI_Irecv(rindices+2*nmax*i,2*nmax,MPI_INT,MPI_ANY_SOURCE,tag2,comm,recv_waits+count++);CHKERRQ(ierr); 469 } 470 471 /* do sends: 472 1) starts[i] gives the starting index in svalues for stuff going to 473 the ith processor 474 */ 475 ierr = PetscMalloc((stash->n+1)*(bs2*sizeof(MatScalar)+2*sizeof(int)),&svalues);CHKERRQ(ierr); 476 sindices = (int*)(svalues + bs2*stash->n); 477 ierr = PetscMalloc(2*(nsends+1)*sizeof(MPI_Request),&send_waits);CHKERRQ(ierr); 478 ierr = PetscMalloc(2*size*sizeof(int),&startv);CHKERRQ(ierr); 479 starti = startv + size; 480 /* use 2 sends the first with all_a, the next with all_i and all_j */ 481 startv[0] = 0; starti[0] = 0; 482 for (i=1; i<size; i++) { 483 startv[i] = startv[i-1] + nprocs[2*i-2]; 484 starti[i] = starti[i-1] + nprocs[2*i-2]*2; 485 } 486 for (i=0; i<stash->n; i++) { 487 j = owner[i]; 488 if (bs2 == 1) { 489 svalues[startv[j]] = stash->array[i]; 490 } else { 491 int k; 492 MatScalar *buf1,*buf2; 493 buf1 = svalues+bs2*startv[j]; 494 buf2 = stash->array+bs2*i; 495 for (k=0; k<bs2; k++){ buf1[k] = buf2[k]; } 496 } 497 sindices[starti[j]] = stash->idx[i]; 498 sindices[starti[j]+nprocs[2*j]] = stash->idy[i]; 499 startv[j]++; 500 starti[j]++; 501 } 502 startv[0] = 0; 503 for (i=1; i<size; i++) { startv[i] = startv[i-1] + nprocs[2*i-2];} 504 for (i=0,count=0; i<size; i++) { 505 if (nprocs[2*i+1]) { 506 ierr = MPI_Isend(svalues+bs2*startv[i],bs2*nprocs[2*i],MPIU_MATSCALAR,i,tag1,comm, 507 send_waits+count++);CHKERRQ(ierr); 508 ierr = MPI_Isend(sindices+2*startv[i],2*nprocs[2*i],MPI_INT,i,tag2,comm, 509 send_waits+count++);CHKERRQ(ierr); 510 } 511 } 512 ierr = PetscFree(owner);CHKERRQ(ierr); 513 ierr = PetscFree(startv);CHKERRQ(ierr); 514 /* This memory is reused in scatter end for a different purpose*/ 515 for (i=0; i<2*size; i++) nprocs[i] = -1; 516 stash->nprocs = nprocs; 517 518 stash->svalues = svalues; stash->rvalues = rvalues; 519 stash->nsends = nsends; stash->nrecvs = nreceives; 520 stash->send_waits = send_waits; stash->recv_waits = recv_waits; 521 stash->rmax = nmax; 522 PetscFunctionReturn(0); 523 } 524 525 /* 526 MatStashScatterGetMesg_Private - This function waits on the receives posted 527 in the function MatStashScatterBegin_Private() and returns one message at 528 a time to the calling function. If no messages are left, it indicates this 529 by setting flg = 0, else it sets flg = 1. 530 531 Input Parameters: 532 stash - the stash 533 534 Output Parameters: 535 nvals - the number of entries in the current message. 536 rows - an array of row indices (or blocked indices) corresponding to the values 537 cols - an array of columnindices (or blocked indices) corresponding to the values 538 vals - the values 539 flg - 0 indicates no more message left, and the current call has no values associated. 540 1 indicates that the current call successfully received a message, and the 541 other output parameters nvals,rows,cols,vals are set appropriately. 542 */ 543 #undef __FUNCT__ 544 #define __FUNCT__ "MatStashScatterGetMesg_Private" 545 int MatStashScatterGetMesg_Private(MatStash *stash,int *nvals,int **rows,int** cols,MatScalar **vals,int *flg) 546 { 547 int i,ierr,*flg_v,i1,i2,*rindices,bs2; 548 MPI_Status recv_status; 549 PetscTruth match_found = PETSC_FALSE; 550 551 PetscFunctionBegin; 552 553 *flg = 0; /* When a message is discovered this is reset to 1 */ 554 /* Return if no more messages to process */ 555 if (stash->nprocessed == stash->nrecvs) { PetscFunctionReturn(0); } 556 557 flg_v = stash->nprocs; 558 bs2 = stash->bs*stash->bs; 559 /* If a matching pair of receieves are found, process them, and return the data to 560 the calling function. Until then keep receiving messages */ 561 while (!match_found) { 562 ierr = MPI_Waitany(2*stash->nrecvs,stash->recv_waits,&i,&recv_status);CHKERRQ(ierr); 563 /* Now pack the received message into a structure which is useable by others */ 564 if (i % 2) { 565 ierr = MPI_Get_count(&recv_status,MPI_INT,nvals);CHKERRQ(ierr); 566 flg_v[2*recv_status.MPI_SOURCE+1] = i/2; 567 *nvals = *nvals/2; /* This message has both row indices and col indices */ 568 } else { 569 ierr = MPI_Get_count(&recv_status,MPIU_MATSCALAR,nvals);CHKERRQ(ierr); 570 flg_v[2*recv_status.MPI_SOURCE] = i/2; 571 *nvals = *nvals/bs2; 572 } 573 574 /* Check if we have both the messages from this proc */ 575 i1 = flg_v[2*recv_status.MPI_SOURCE]; 576 i2 = flg_v[2*recv_status.MPI_SOURCE+1]; 577 if (i1 != -1 && i2 != -1) { 578 rindices = (int*)(stash->rvalues + bs2*stash->rmax*stash->nrecvs); 579 *rows = rindices + 2*i2*stash->rmax; 580 *cols = *rows + *nvals; 581 *vals = stash->rvalues + i1*bs2*stash->rmax; 582 *flg = 1; 583 stash->nprocessed ++; 584 match_found = PETSC_TRUE; 585 } 586 } 587 PetscFunctionReturn(0); 588 } 589