1c094ef40SMatthew G. Knepley #include <petsc/private/matimpl.h> /*I "petscmat.h" I*/ 2e8f14785SLisandro Dalcin #include <petsc/private/hashsetij.h> 3c094ef40SMatthew G. Knepley 4c094ef40SMatthew G. Knepley typedef struct { 5e8f14785SLisandro Dalcin PetscHSetIJ ht; 6c094ef40SMatthew G. Knepley PetscInt *dnz, *onz; 7c09129f1SStefano Zampini PetscInt *dnzu, *onzu; 8f8f6e9aeSStefano Zampini PetscBool nooffproc; 9*fcc385faSPatrick Sanan PetscBool used; 10c094ef40SMatthew G. Knepley } Mat_Preallocator; 11c094ef40SMatthew G. Knepley 12c094ef40SMatthew G. Knepley PetscErrorCode MatDestroy_Preallocator(Mat A) 13c094ef40SMatthew G. Knepley { 14c094ef40SMatthew G. Knepley Mat_Preallocator *p = (Mat_Preallocator *) A->data; 15c094ef40SMatthew G. Knepley PetscErrorCode ierr; 16c094ef40SMatthew G. Knepley 17c094ef40SMatthew G. Knepley PetscFunctionBegin; 18c094ef40SMatthew G. Knepley ierr = MatStashDestroy_Private(&A->stash);CHKERRQ(ierr); 19e8f14785SLisandro Dalcin ierr = PetscHSetIJDestroy(&p->ht);CHKERRQ(ierr); 20c09129f1SStefano Zampini ierr = PetscFree4(p->dnz, p->onz, p->dnzu, p->onzu);CHKERRQ(ierr); 21c094ef40SMatthew G. Knepley ierr = PetscFree(A->data);CHKERRQ(ierr); 22f4259b30SLisandro Dalcin ierr = PetscObjectChangeTypeName((PetscObject) A, NULL);CHKERRQ(ierr); 23c094ef40SMatthew G. Knepley ierr = PetscObjectComposeFunction((PetscObject) A, "MatPreallocatorPreallocate_C", NULL);CHKERRQ(ierr); 24c094ef40SMatthew G. Knepley PetscFunctionReturn(0); 25c094ef40SMatthew G. Knepley } 26c094ef40SMatthew G. Knepley 27c094ef40SMatthew G. Knepley PetscErrorCode MatSetUp_Preallocator(Mat A) 28c094ef40SMatthew G. Knepley { 29c094ef40SMatthew G. Knepley Mat_Preallocator *p = (Mat_Preallocator *) A->data; 305dca1104SStefano Zampini PetscInt m, bs, mbs; 31c094ef40SMatthew G. Knepley PetscErrorCode ierr; 32c094ef40SMatthew G. Knepley 33c094ef40SMatthew G. Knepley PetscFunctionBegin; 34c094ef40SMatthew G. Knepley ierr = PetscLayoutSetUp(A->rmap);CHKERRQ(ierr); 35c094ef40SMatthew G. Knepley ierr = PetscLayoutSetUp(A->cmap);CHKERRQ(ierr); 36c094ef40SMatthew G. Knepley ierr = MatGetLocalSize(A, &m, NULL);CHKERRQ(ierr); 37e8f14785SLisandro Dalcin ierr = PetscHSetIJCreate(&p->ht);CHKERRQ(ierr); 38c094ef40SMatthew G. Knepley ierr = MatGetBlockSize(A, &bs);CHKERRQ(ierr); 398011973bSJunchao Zhang /* Do not bother bstash since MatPreallocator does not implement MatSetValuesBlocked */ 408011973bSJunchao Zhang ierr = MatStashCreate_Private(PetscObjectComm((PetscObject) A), 1, &A->stash);CHKERRQ(ierr); 415dca1104SStefano Zampini /* arrays are for blocked rows/cols */ 425dca1104SStefano Zampini mbs = m/bs; 435dca1104SStefano Zampini ierr = PetscCalloc4(mbs, &p->dnz, mbs, &p->onz, mbs, &p->dnzu, mbs, &p->onzu);CHKERRQ(ierr); 44c094ef40SMatthew G. Knepley PetscFunctionReturn(0); 45c094ef40SMatthew G. Knepley } 46c094ef40SMatthew G. Knepley 47c094ef40SMatthew G. Knepley PetscErrorCode MatSetValues_Preallocator(Mat A, PetscInt m, const PetscInt *rows, PetscInt n, const PetscInt *cols, const PetscScalar *values, InsertMode addv) 48c094ef40SMatthew G. Knepley { 49c094ef40SMatthew G. Knepley Mat_Preallocator *p = (Mat_Preallocator *) A->data; 505dca1104SStefano Zampini PetscInt rStart, rEnd, r, cStart, cEnd, c, bs; 51c094ef40SMatthew G. Knepley PetscErrorCode ierr; 52c094ef40SMatthew G. Knepley 53c094ef40SMatthew G. Knepley PetscFunctionBegin; 545dca1104SStefano Zampini ierr = MatGetBlockSize(A, &bs);CHKERRQ(ierr); 55c094ef40SMatthew G. Knepley ierr = MatGetOwnershipRange(A, &rStart, &rEnd);CHKERRQ(ierr); 56c094ef40SMatthew G. Knepley ierr = MatGetOwnershipRangeColumn(A, &cStart, &cEnd);CHKERRQ(ierr); 57c094ef40SMatthew G. Knepley for (r = 0; r < m; ++r) { 58e8f14785SLisandro Dalcin PetscHashIJKey key; 59e8f14785SLisandro Dalcin PetscBool missing; 60c094ef40SMatthew G. Knepley 61e8f14785SLisandro Dalcin key.i = rows[r]; 62e8f14785SLisandro Dalcin if (key.i < 0) continue; 63e8f14785SLisandro Dalcin if ((key.i < rStart) || (key.i >= rEnd)) { 64e8f14785SLisandro Dalcin ierr = MatStashValuesRow_Private(&A->stash, key.i, n, cols, values, PETSC_FALSE);CHKERRQ(ierr); 655dca1104SStefano Zampini } else { /* Hash table is for blocked rows/cols */ 665dca1104SStefano Zampini key.i = rows[r]/bs; 67c094ef40SMatthew G. Knepley for (c = 0; c < n; ++c) { 685dca1104SStefano Zampini key.j = cols[c]/bs; 69e8f14785SLisandro Dalcin if (key.j < 0) continue; 70e8f14785SLisandro Dalcin ierr = PetscHSetIJQueryAdd(p->ht, key, &missing);CHKERRQ(ierr); 71c094ef40SMatthew G. Knepley if (missing) { 725dca1104SStefano Zampini if ((key.j >= cStart/bs) && (key.j < cEnd/bs)) { 735dca1104SStefano Zampini ++p->dnz[key.i-rStart/bs]; 745dca1104SStefano Zampini if (key.j >= key.i) ++p->dnzu[key.i-rStart/bs]; 75c09129f1SStefano Zampini } else { 765dca1104SStefano Zampini ++p->onz[key.i-rStart/bs]; 775dca1104SStefano Zampini if (key.j >= key.i) ++p->onzu[key.i-rStart/bs]; 78c09129f1SStefano Zampini } 79c094ef40SMatthew G. Knepley } 80c094ef40SMatthew G. Knepley } 81c094ef40SMatthew G. Knepley } 82c094ef40SMatthew G. Knepley } 83c094ef40SMatthew G. Knepley PetscFunctionReturn(0); 84c094ef40SMatthew G. Knepley } 85c094ef40SMatthew G. Knepley 86c094ef40SMatthew G. Knepley PetscErrorCode MatAssemblyBegin_Preallocator(Mat A, MatAssemblyType type) 87c094ef40SMatthew G. Knepley { 88c094ef40SMatthew G. Knepley PetscInt nstash, reallocs; 89c094ef40SMatthew G. Knepley PetscErrorCode ierr; 90c094ef40SMatthew G. Knepley 91c094ef40SMatthew G. Knepley PetscFunctionBegin; 92c094ef40SMatthew G. Knepley ierr = MatStashScatterBegin_Private(A, &A->stash, A->rmap->range);CHKERRQ(ierr); 93c094ef40SMatthew G. Knepley ierr = MatStashGetInfo_Private(&A->stash, &nstash, &reallocs);CHKERRQ(ierr); 94c094ef40SMatthew G. Knepley ierr = PetscInfo2(A, "Stash has %D entries, uses %D mallocs.\n", nstash, reallocs);CHKERRQ(ierr); 95c094ef40SMatthew G. Knepley PetscFunctionReturn(0); 96c094ef40SMatthew G. Knepley } 97c094ef40SMatthew G. Knepley 98c094ef40SMatthew G. Knepley PetscErrorCode MatAssemblyEnd_Preallocator(Mat A, MatAssemblyType type) 99c094ef40SMatthew G. Knepley { 100c094ef40SMatthew G. Knepley PetscScalar *val; 101c094ef40SMatthew G. Knepley PetscInt *row, *col; 102c094ef40SMatthew G. Knepley PetscInt i, j, rstart, ncols, flg; 103c094ef40SMatthew G. Knepley PetscMPIInt n; 104f8f6e9aeSStefano Zampini Mat_Preallocator *p = (Mat_Preallocator *) A->data; 105c094ef40SMatthew G. Knepley PetscErrorCode ierr; 106c094ef40SMatthew G. Knepley 107c094ef40SMatthew G. Knepley PetscFunctionBegin; 108f8f6e9aeSStefano Zampini p->nooffproc = PETSC_TRUE; 109c094ef40SMatthew G. Knepley while (1) { 110c094ef40SMatthew G. Knepley ierr = MatStashScatterGetMesg_Private(&A->stash, &n, &row, &col, &val, &flg);CHKERRQ(ierr); 111f8f6e9aeSStefano Zampini if (flg) p->nooffproc = PETSC_FALSE; 112c094ef40SMatthew G. Knepley if (!flg) break; 113c094ef40SMatthew G. Knepley 114c094ef40SMatthew G. Knepley for (i = 0; i < n;) { 115c094ef40SMatthew G. Knepley /* Now identify the consecutive vals belonging to the same row */ 116c094ef40SMatthew G. Knepley for (j = i, rstart = row[j]; j < n; j++) { 117c094ef40SMatthew G. Knepley if (row[j] != rstart) break; 118c094ef40SMatthew G. Knepley } 119c094ef40SMatthew G. Knepley if (j < n) ncols = j-i; 120c094ef40SMatthew G. Knepley else ncols = n-i; 121c094ef40SMatthew G. Knepley /* Now assemble all these values with a single function call */ 122c094ef40SMatthew G. Knepley ierr = MatSetValues_Preallocator(A, 1, row+i, ncols, col+i, val+i, INSERT_VALUES);CHKERRQ(ierr); 123c094ef40SMatthew G. Knepley i = j; 124c094ef40SMatthew G. Knepley } 125c094ef40SMatthew G. Knepley } 126c094ef40SMatthew G. Knepley ierr = MatStashScatterEnd_Private(&A->stash);CHKERRQ(ierr); 127820f2d46SBarry Smith ierr = MPIU_Allreduce(MPI_IN_PLACE,&p->nooffproc,1,MPIU_BOOL,MPI_LAND,PetscObjectComm((PetscObject)A));CHKERRMPI(ierr); 128c094ef40SMatthew G. Knepley PetscFunctionReturn(0); 129c094ef40SMatthew G. Knepley } 130c094ef40SMatthew G. Knepley 131c094ef40SMatthew G. Knepley PetscErrorCode MatView_Preallocator(Mat A, PetscViewer viewer) 132c094ef40SMatthew G. Knepley { 133c094ef40SMatthew G. Knepley PetscFunctionBegin; 134c094ef40SMatthew G. Knepley PetscFunctionReturn(0); 135c094ef40SMatthew G. Knepley } 136c094ef40SMatthew G. Knepley 137c094ef40SMatthew G. Knepley PetscErrorCode MatSetOption_Preallocator(Mat A, MatOption op, PetscBool flg) 138c094ef40SMatthew G. Knepley { 139c094ef40SMatthew G. Knepley PetscFunctionBegin; 140c094ef40SMatthew G. Knepley PetscFunctionReturn(0); 141c094ef40SMatthew G. Knepley } 142c094ef40SMatthew G. Knepley 143c094ef40SMatthew G. Knepley PetscErrorCode MatPreallocatorPreallocate_Preallocator(Mat mat, PetscBool fill, Mat A) 144c094ef40SMatthew G. Knepley { 145c094ef40SMatthew G. Knepley Mat_Preallocator *p = (Mat_Preallocator *) mat->data; 146c094ef40SMatthew G. Knepley PetscInt bs; 147c094ef40SMatthew G. Knepley PetscErrorCode ierr; 148c094ef40SMatthew G. Knepley 149c094ef40SMatthew G. Knepley PetscFunctionBegin; 150*fcc385faSPatrick Sanan if (p->used) SETERRQ(PetscObjectComm((PetscObject)mat),PETSC_ERR_SUP,"MatPreallocatorPreallocate() can only be used once for a give MatPreallocator object. Consider using MatDuplicate() after preallocation."); 151*fcc385faSPatrick Sanan p->used = PETSC_TRUE; 152880e7892SJed Brown if (!fill) {ierr = PetscHSetIJDestroy(&p->ht);CHKERRQ(ierr);} 153c094ef40SMatthew G. Knepley ierr = MatGetBlockSize(mat, &bs);CHKERRQ(ierr); 154c09129f1SStefano Zampini ierr = MatXAIJSetPreallocation(A, bs, p->dnz, p->onz, p->dnzu, p->onzu);CHKERRQ(ierr); 155050c3c49SStefano Zampini ierr = MatSetUp(A);CHKERRQ(ierr); 156c094ef40SMatthew G. Knepley ierr = MatSetOption(A, MAT_NEW_NONZERO_ALLOCATION_ERR, PETSC_TRUE);CHKERRQ(ierr); 157f8f6e9aeSStefano Zampini ierr = MatSetOption(A, MAT_NO_OFF_PROC_ENTRIES, p->nooffproc);CHKERRQ(ierr); 158380bf85aSDave May if (fill) { 159380bf85aSDave May PetscHashIter hi; 160380bf85aSDave May PetscHashIJKey key; 161380bf85aSDave May PetscScalar *zeros; 162880e7892SJed Brown PetscInt n,maxrow=1,*cols,rStart,rEnd,*rowstarts; 163380bf85aSDave May 164880e7892SJed Brown ierr = MatGetOwnershipRange(A, &rStart, &rEnd);CHKERRQ(ierr); 165880e7892SJed Brown // Ownership range is in terms of scalar entries, but we deal with blocks 166880e7892SJed Brown rStart /= bs; 167880e7892SJed Brown rEnd /= bs; 168146543f8SJed Brown ierr = PetscHSetIJGetSize(p->ht,&n);CHKERRQ(ierr); 169880e7892SJed Brown ierr = PetscMalloc2(n,&cols,rEnd-rStart+1,&rowstarts);CHKERRQ(ierr); 170880e7892SJed Brown rowstarts[0] = 0; 171880e7892SJed Brown for (PetscInt i=0; i<rEnd-rStart; i++) { 172880e7892SJed Brown rowstarts[i+1] = rowstarts[i] + p->dnz[i] + p->onz[i]; 173880e7892SJed Brown maxrow = PetscMax(maxrow, p->dnz[i] + p->onz[i]); 174880e7892SJed Brown } 175880e7892SJed Brown if (rowstarts[rEnd-rStart] != n) SETERRQ2(PETSC_COMM_SELF,PETSC_ERR_PLIB,"Hash claims %D entries, but dnz+onz counts %D",n,rowstarts[rEnd-rStart]); 176880e7892SJed Brown 177380bf85aSDave May PetscHashIterBegin(p->ht,hi); 178146543f8SJed Brown for (PetscInt i=0; !PetscHashIterAtEnd(p->ht,hi); i++) { 179380bf85aSDave May PetscHashIterGetKey(p->ht,hi,key); 180880e7892SJed Brown PetscInt lrow = key.i - rStart; 181880e7892SJed Brown cols[rowstarts[lrow]] = key.j; 182880e7892SJed Brown rowstarts[lrow]++; 183380bf85aSDave May PetscHashIterNext(p->ht,hi); 184146543f8SJed Brown } 185146543f8SJed Brown ierr = PetscHSetIJDestroy(&p->ht);CHKERRQ(ierr); 186146543f8SJed Brown 187146543f8SJed Brown ierr = PetscCalloc1(maxrow*bs*bs,&zeros);CHKERRQ(ierr); 188880e7892SJed Brown for (PetscInt i=0; i<rEnd-rStart; i++) { 189880e7892SJed Brown PetscInt grow = rStart + i; 190880e7892SJed Brown PetscInt end = rowstarts[i], start = end - p->dnz[i] - p->onz[i]; 191146543f8SJed Brown ierr = PetscSortInt(end-start,&cols[start]);CHKERRQ(ierr); 192880e7892SJed Brown ierr = MatSetValuesBlocked(A, 1, &grow, end-start, &cols[start], zeros, INSERT_VALUES);CHKERRQ(ierr); 193380bf85aSDave May } 194380bf85aSDave May ierr = PetscFree(zeros);CHKERRQ(ierr); 195880e7892SJed Brown ierr = PetscFree2(cols,rowstarts);CHKERRQ(ierr); 196380bf85aSDave May 197380bf85aSDave May ierr = MatAssemblyBegin(A,MAT_FINAL_ASSEMBLY);CHKERRQ(ierr); 198380bf85aSDave May ierr = MatAssemblyEnd(A,MAT_FINAL_ASSEMBLY);CHKERRQ(ierr); 199380bf85aSDave May } 200c094ef40SMatthew G. Knepley PetscFunctionReturn(0); 201c094ef40SMatthew G. Knepley } 202c094ef40SMatthew G. Knepley 203c094ef40SMatthew G. Knepley /*@ 204380bf85aSDave May MatPreallocatorPreallocate - Preallocates the A matrix, using information from mat, optionally filling A with zeros 205c094ef40SMatthew G. Knepley 206d8d19677SJose E. Roman Input Parameters: 207c094ef40SMatthew G. Knepley + mat - the preallocator 208380bf85aSDave May . fill - fill the matrix with zeros 209380bf85aSDave May - A - the matrix to be preallocated 210c094ef40SMatthew G. Knepley 211380bf85aSDave May Notes: 212380bf85aSDave May This Mat implementation provides a helper utility to define the correct 213380bf85aSDave May preallocation data for a given nonzero structure. Use this object like a 214380bf85aSDave May regular matrix, e.g. loop over the nonzero structure of the matrix and 215380bf85aSDave May call MatSetValues() or MatSetValuesBlocked() to indicate the nonzero locations. 216a5b23f4aSJose E. Roman The matrix entries provided to MatSetValues() will be ignored, it only uses 217380bf85aSDave May the row / col indices provided to determine the information required to be 218380bf85aSDave May passed to MatXAIJSetPreallocation(). Once you have looped over the nonzero 219380bf85aSDave May structure, you must call MatAssemblyBegin(), MatAssemblyEnd() on mat. 220380bf85aSDave May 221380bf85aSDave May After you have assembled the preallocator matrix (mat), call MatPreallocatorPreallocate() 222380bf85aSDave May to define the preallocation information on the matrix (A). Setting the parameter 223380bf85aSDave May fill = PETSC_TRUE will insert zeros into the matrix A. Internally MatPreallocatorPreallocate() 224380bf85aSDave May will call MatSetOption(A, MAT_NEW_NONZERO_ALLOCATION_ERR, PETSC_TRUE); 225c094ef40SMatthew G. Knepley 226*fcc385faSPatrick Sanan This function may only be called once for a given MatPreallocator object. If 227*fcc385faSPatrick Sanan multiple Mats need to be preallocated, consider using MatDuplicate() after 228*fcc385faSPatrick Sanan this function. 229*fcc385faSPatrick Sanan 230c094ef40SMatthew G. Knepley Level: advanced 231c094ef40SMatthew G. Knepley 232c094ef40SMatthew G. Knepley .seealso: MATPREALLOCATOR 233c094ef40SMatthew G. Knepley @*/ 234c094ef40SMatthew G. Knepley PetscErrorCode MatPreallocatorPreallocate(Mat mat, PetscBool fill, Mat A) 235c094ef40SMatthew G. Knepley { 236c094ef40SMatthew G. Knepley PetscErrorCode ierr; 237c094ef40SMatthew G. Knepley 238c094ef40SMatthew G. Knepley PetscFunctionBegin; 239c094ef40SMatthew G. Knepley PetscValidHeaderSpecific(mat, MAT_CLASSID, 1); 240c094ef40SMatthew G. Knepley PetscValidHeaderSpecific(A, MAT_CLASSID, 3); 241c094ef40SMatthew G. Knepley ierr = PetscUseMethod(mat, "MatPreallocatorPreallocate_C", (Mat,PetscBool,Mat),(mat,fill,A));CHKERRQ(ierr); 242c094ef40SMatthew G. Knepley PetscFunctionReturn(0); 243c094ef40SMatthew G. Knepley } 244c094ef40SMatthew G. Knepley 245c094ef40SMatthew G. Knepley /*MC 246c094ef40SMatthew G. Knepley MATPREALLOCATOR - MATPREALLOCATOR = "preallocator" - A matrix type to be used for computing a matrix preallocation. 247c094ef40SMatthew G. Knepley 248c094ef40SMatthew G. Knepley Operations Provided: 249c094ef40SMatthew G. Knepley . MatSetValues() 250c094ef40SMatthew G. Knepley 251c094ef40SMatthew G. Knepley Options Database Keys: 252c094ef40SMatthew G. Knepley . -mat_type preallocator - sets the matrix type to "preallocator" during a call to MatSetFromOptions() 253c094ef40SMatthew G. Knepley 254c094ef40SMatthew G. Knepley Level: advanced 255c094ef40SMatthew G. Knepley 256c74f52fcSPatrick Sanan .seealso: Mat, MatPreallocatorPreallocate() 257c094ef40SMatthew G. Knepley 258c094ef40SMatthew G. Knepley M*/ 259c094ef40SMatthew G. Knepley 260c094ef40SMatthew G. Knepley PETSC_EXTERN PetscErrorCode MatCreate_Preallocator(Mat A) 261c094ef40SMatthew G. Knepley { 262c094ef40SMatthew G. Knepley Mat_Preallocator *p; 263c094ef40SMatthew G. Knepley PetscErrorCode ierr; 264c094ef40SMatthew G. Knepley 265c094ef40SMatthew G. Knepley PetscFunctionBegin; 266c094ef40SMatthew G. Knepley ierr = PetscNewLog(A, &p);CHKERRQ(ierr); 267c094ef40SMatthew G. Knepley A->data = (void *) p; 268c094ef40SMatthew G. Knepley 269c094ef40SMatthew G. Knepley p->ht = NULL; 270c094ef40SMatthew G. Knepley p->dnz = NULL; 271c094ef40SMatthew G. Knepley p->onz = NULL; 272c09129f1SStefano Zampini p->dnzu = NULL; 273c09129f1SStefano Zampini p->onzu = NULL; 274*fcc385faSPatrick Sanan p->used = PETSC_FALSE; 275c094ef40SMatthew G. Knepley 276c094ef40SMatthew G. Knepley /* matrix ops */ 277c094ef40SMatthew G. Knepley ierr = PetscMemzero(A->ops, sizeof(struct _MatOps));CHKERRQ(ierr); 278c09129f1SStefano Zampini 279c094ef40SMatthew G. Knepley A->ops->destroy = MatDestroy_Preallocator; 280c094ef40SMatthew G. Knepley A->ops->setup = MatSetUp_Preallocator; 281c094ef40SMatthew G. Knepley A->ops->setvalues = MatSetValues_Preallocator; 282c094ef40SMatthew G. Knepley A->ops->assemblybegin = MatAssemblyBegin_Preallocator; 283c094ef40SMatthew G. Knepley A->ops->assemblyend = MatAssemblyEnd_Preallocator; 284c094ef40SMatthew G. Knepley A->ops->view = MatView_Preallocator; 285c094ef40SMatthew G. Knepley A->ops->setoption = MatSetOption_Preallocator; 2865dca1104SStefano Zampini A->ops->setblocksizes = MatSetBlockSizes_Default; /* once set, user is not allowed to change the block sizes */ 287c094ef40SMatthew G. Knepley 288c094ef40SMatthew G. Knepley /* special MATPREALLOCATOR functions */ 289c094ef40SMatthew G. Knepley ierr = PetscObjectComposeFunction((PetscObject) A, "MatPreallocatorPreallocate_C", MatPreallocatorPreallocate_Preallocator);CHKERRQ(ierr); 290c094ef40SMatthew G. Knepley ierr = PetscObjectChangeTypeName((PetscObject) A, MATPREALLOCATOR);CHKERRQ(ierr); 291c094ef40SMatthew G. Knepley PetscFunctionReturn(0); 292c094ef40SMatthew G. Knepley } 293