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