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