Actual source code: ex19.c

  1: /*$Id: ex19.c,v 1.19 2001/04/10 19:37:05 bsmith Exp bsmith $*/

  3: static char help[] = "Nonlinear driven cavity with multigrid in 2d.n
  4:   n
  5: The 2D driven cavity problem is solved in a velocity-vorticity formulation.n
  6: The flow can be driven with the lid or with bouyancy or both:n
  7:   -lidvelocity <lid>, where <lid> = dimensionless velocity of lidn
  8:   -grashof <gr>, where <gr> = dimensionless temperature gradientn
  9:   -prandtl <pr>, where <pr> = dimensionless thermal/momentum diffusity ration
 10:   -contours : draw contour plots of solutionnn";

 12: /*T
 13:    Concepts: SNES^solving a system of nonlinear equations (parallel multicomponent example);
 14:    Concepts: DA^using distributed arrays;
 15:    Concepts: multicomponent
 16:    Processors: n
 17: T*/

 19: /* ------------------------------------------------------------------------

 21:     We thank David E. Keyes for contributing the driven cavity discretization
 22:     within this example code.

 24:     This problem is modeled by the partial differential equation system
 25:   
 26:         - Lap(U) - Grad_y(Omega) = 0
 27:         - Lap(V) + Grad_x(Omega) = 0
 28:         - Lap(Omega) + Div([U*Omega,V*Omega]) - GR*Grad_x(T) = 0
 29:         - Lap(T) + PR*Div([U*T,V*T]) = 0

 31:     in the unit square, which is uniformly discretized in each of x and
 32:     y in this simple encoding.

 34:     No-slip, rigid-wall Dirichlet conditions are used for [U,V].
 35:     Dirichlet conditions are used for Omega, based on the definition of
 36:     vorticity: Omega = - Grad_y(U) + Grad_x(V), where along each
 37:     constant coordinate boundary, the tangential derivative is zero.
 38:     Dirichlet conditions are used for T on the left and right walls,
 39:     and insulation homogeneous Neumann conditions are used for T on
 40:     the top and bottom walls. 

 42:     A finite difference approximation with the usual 5-point stencil 
 43:     is used to discretize the boundary value problem to obtain a 
 44:     nonlinear system of equations.  Upwinding is used for the divergence
 45:     (convective) terms and central for the gradient (source) terms.
 46:     
 47:     The Jacobian can be either
 48:       * formed via finite differencing using coloring (the default), or
 49:       * applied matrix-free via the option -snes_mf 
 50:         (for larger grid problems this variant may not converge 
 51:         without a preconditioner due to ill-conditioning).

 53:   ------------------------------------------------------------------------- */

 55: /* 
 56:    Include "petscda.h" so that we can use distributed arrays (DAs).
 57:    Include "petscsnes.h" so that we can use SNES solvers.  Note that this
 58:    file automatically includes:
 59:      petsc.h       - base PETSc routines   petscvec.h - vectors
 60:      petscsys.h    - system routines       petscmat.h - matrices
 61:      petscis.h     - index sets            petscksp.h - Krylov subspace methods
 62:      petscviewer.h - viewers               petscpc.h  - preconditioners
 63:      petscsles.h   - linear solvers 
 64: */
 65:  #include petscsnes.h
 66:  #include petscda.h

 68: /* 
 69:    User-defined routines and data structures
 70: */
 71: typedef struct {
 72:   Scalar u,v,omega,temp;
 73: } Field;

 75: extern int FormInitialGuess(SNES,Vec,void*);
 76: extern int FormFunction(SNES,Vec,Vec,void*);
 77: extern int FormFunctionLocal(Field**x,Field**f,DALocalInfo*info,void*);

 79: typedef struct {
 80:    double     lidvelocity,prandtl,grashof;  /* physical parameters */
 81:    PetscTruth draw_contours;                /* flag - 1 indicates drawing contours */
 82: } AppCtx;

 84: int main(int argc,char **argv)
 85: {
 86:   DMMG       *dmmg;               /* multilevel grid structure */
 87:   AppCtx     user;                /* user-defined work context */
 88:   int        mx,my,its;
 89:   int        ierr;
 90:   MPI_Comm   comm;
 91:   SNES       snes;
 92:   DA         da;
 93:   PetscTruth localfunction = PETSC_TRUE;

 95:   PetscInitialize(&argc,&argv,(char *)0,help);
 96:   comm = PETSC_COMM_WORLD;


 99:   PreLoadBegin(PETSC_TRUE,"SetUp");
100:     DMMGCreate(comm,2,&user,&dmmg);


103:     /*
104:       Create distributed array multigrid object (DMMG) to manage parallel grid and vectors
105:       for principal unknowns (x) and governing residuals (f)
106:     */
107:     DACreate2d(comm,DA_NONPERIODIC,DA_STENCIL_STAR,4,4,PETSC_DECIDE,PETSC_DECIDE,4,1,0,0,&da);
108:     DMMGSetDM(dmmg,(DM)da);
109:     DADestroy(da);

111:     DAGetInfo(DMMGGetDA(dmmg),0,&mx,&my,PETSC_IGNORE,PETSC_IGNORE,PETSC_IGNORE,PETSC_IGNORE,PETSC_IGNORE,PETSC_IGNORE,
112:                      PETSC_IGNORE,PETSC_IGNORE);
113:     /* 
114:      Problem parameters (velocity of lid, prandtl, and grashof numbers)
115:     */
116:     user.lidvelocity = 1.0/(mx*my);
117:     user.prandtl     = 1.0;
118:     user.grashof     = 1.0;
119:     PetscOptionsGetDouble(PETSC_NULL,"-lidvelocity",&user.lidvelocity,PETSC_NULL);
120:     PetscOptionsGetDouble(PETSC_NULL,"-prandtl",&user.prandtl,PETSC_NULL);
121:     PetscOptionsGetDouble(PETSC_NULL,"-grashof",&user.grashof,PETSC_NULL);
122:     PetscOptionsHasName(PETSC_NULL,"-contours",&user.draw_contours);

124:     DASetFieldName(DMMGGetDA(dmmg),0,"x-velocity");
125:     DASetFieldName(DMMGGetDA(dmmg),1,"y-velocity");
126:     DASetFieldName(DMMGGetDA(dmmg),2,"Omega");
127:     DASetFieldName(DMMGGetDA(dmmg),3,"temperature");

129:     /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
130:        Create user context, set problem data, create vector data structures.
131:        Also, compute the initial guess.
132:        - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

134:     /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
135:        Create nonlinear solver context
136:        - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

138:     PetscOptionsGetLogical(PETSC_NULL,"-localfunction",&localfunction,PETSC_IGNORE);
139:     if (localfunction) {
140:       DMMGSetSNESLocal(dmmg,(int(*)(Scalar**,Scalar**,DALocalInfo*,void*))FormFunctionLocal,0);
141:     } else {
142:       DMMGSetSNES(dmmg,FormFunction,0);
143:     }

145:     PetscPrintf(comm,"lid velocity = %g, prandtl # = %g, grashof # = %gn",
146:                        user.lidvelocity,user.prandtl,user.grashof);


149:     /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
150:        Solve the nonlinear system
151:        - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
152:     DMMGSetInitialGuess(dmmg,FormInitialGuess);

154:   PreLoadStage("Solve");
155:     DMMGSolve(dmmg);

157:     snes = DMMGGetSNES(dmmg);
158:     SNESGetIterationNumber(snes,&its);
159:     PetscPrintf(comm,"Number of Newton iterations = %dn", its);

161:     /*
162:       Visualize solution
163:     */

165:     if (user.draw_contours) {
166:       VecView(DMMGGetx(dmmg),PETSC_VIEWER_DRAW_WORLD);
167:     }

169:     /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
170:        Free work space.  All PETSc objects should be destroyed when they
171:        are no longer needed.
172:        - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

174:     DMMGDestroy(dmmg);
175:   PreLoadEnd();

177:   PetscFinalize();
178:   return 0;
179: }

181: /* ------------------------------------------------------------------- */


184: /* 
185:    FormInitialGuess - Forms initial approximation.

187:    Input Parameters:
188:    user - user-defined application context
189:    X - vector

191:    Output Parameter:
192:    X - vector
193:  */
194: int FormInitialGuess(SNES snes,Vec X,void *ptr)
195: {
196:   DMMG    dmmg = (DMMG)ptr;
197:   AppCtx  *user = (AppCtx*)dmmg->user;
198:   DA      da = (DA)dmmg->dm;
199:   int     i,j,mx,ierr,xs,ys,xm,ym;
200:   double  grashof,dx;
201:   Field   **x;

203:   grashof = user->grashof;

205:   DAGetInfo(da,0,&mx,0,0,0,0,0,0,0,0,0);
206:   dx  = 1.0/(mx-1);

208:   /*
209:      Get local grid boundaries (for 2-dimensional DA):
210:        xs, ys   - starting grid indices (no ghost points)
211:        xm, ym   - widths of local grid (no ghost points)
212:   */
213:   DAGetCorners(da,&xs,&ys,PETSC_NULL,&xm,&ym,PETSC_NULL);

215:   /*
216:      Get a pointer to vector data.
217:        - For default PETSc vectors, VecGetArray() returns a pointer to
218:          the data array.  Otherwise, the routine is implementation dependent.
219:        - You MUST call VecRestoreArray() when you no longer need access to
220:          the array.
221:   */
222:   DAVecGetArray(da,X,(void**)&x);

224:   /*
225:      Compute initial guess over the locally owned part of the grid
226:      Initial condition is motionless fluid and equilibrium temperature
227:   */
228:   for (j=ys; j<ys+ym; j++) {
229:     for (i=xs; i<xs+xm; i++) {
230:       x[j][i].u     = 0.0;
231:       x[j][i].v     = 0.0;
232:       x[j][i].omega = 0.0;
233:       x[j][i].temp  = (grashof>0)*i*dx;
234:     }
235:   }

237:   /*
238:      Restore vector
239:   */
240:   DAVecRestoreArray(da,X,(void**)&x);
241:   return 0;
242: }
243: /* ------------------------------------------------------------------- */
244: /* 
245:    FormFunction - Evaluates the nonlinear function, F(x).

247:    Input Parameters:
248: .  snes - the SNES context
249: .  X - input vector
250: .  ptr - optional user-defined context, as set by SNESSetFunction()

252:    Output Parameter:
253: .  F - function vector

255:    Notes:
256:    We process the boundary nodes before handling the interior
257:    nodes, so that no conditional statements are needed within the
258:    double loop over the local grid indices. 
259:  */
260: int FormFunction(SNES snes,Vec X,Vec F,void *ptr)
261: {
262:   DMMG    dmmg = (DMMG)ptr;
263:   AppCtx  *user = (AppCtx*)dmmg->user;
264:   int     ierr,i,j,mx,my,xs,ys,xm,ym;
265:   int     xints,xinte,yints,yinte;
266:   double  two = 2.0,one = 1.0,p5 = 0.5,hx,hy,dhx,dhy,hxdhy,hydhx;
267:   double  grashof,prandtl,lid;
268:   Scalar  u,uxx,uyy,vx,vy,avx,avy,vxp,vxm,vyp,vym;
269:   Field   **x,**f;
270:   Vec     localX;
271:   DA      da = (DA)dmmg->dm;

273:   DAGetLocalVector((DA)dmmg->dm,&localX);
274:   DAGetInfo(da,0,&mx,&my,0,0,0,0,0,0,0,0);

276:   grashof = user->grashof;
277:   prandtl = user->prandtl;
278:   lid     = user->lidvelocity;

280:   /* 
281:      Define mesh intervals ratios for uniform grid.
282:      [Note: FD formulae below are normalized by multiplying through by
283:      local volume element to obtain coefficients O(1) in two dimensions.]
284:   */
285:   dhx = (double)(mx-1);     dhy = (double)(my-1);
286:   hx = one/dhx;             hy = one/dhy;
287:   hxdhy = hx*dhy;           hydhx = hy*dhx;

289:   /*
290:      Scatter ghost points to local vector, using the 2-step process
291:         DAGlobalToLocalBegin(), DAGlobalToLocalEnd().
292:      By placing code between these two statements, computations can be
293:      done while messages are in transition.
294:   */
295:   DAGlobalToLocalBegin(da,X,INSERT_VALUES,localX);
296:   DAGlobalToLocalEnd(da,X,INSERT_VALUES,localX);

298:   /*
299:      Get pointers to vector data
300:   */
301:   DAVecGetArray((DA)dmmg->dm,localX,(void**)&x);
302:   DAVecGetArray((DA)dmmg->dm,F,(void**)&f);

304:   /*
305:      Get local grid boundaries
306:   */
307:   DAGetCorners(da,&xs,&ys,PETSC_NULL,&xm,&ym,PETSC_NULL);

309:   /*
310:      Compute function over the locally owned part of the grid
311:      (physical corner points are set twice to avoid more conditionals).
312:   */
313:   xints = xs; xinte = xs+xm; yints = ys; yinte = ys+ym;

315:   /* Test whether we are on the bottom edge of the global array */
316:   if (yints == 0) {
317:     j = 0;
318:     yints = yints + 1;
319:     /* bottom edge */
320:     for (i=xs; i<xs+xm; i++) {
321:         f[j][i].u     = x[j][i].u;
322:         f[j][i].v     = x[j][i].v;
323:         f[j][i].omega = x[j][i].omega + (x[j+1][i].u - x[j][i].u)*dhy;
324:         f[j][i].temp  = x[j][i].temp-x[j+1][i].temp;
325:     }
326:   }

328:   /* Test whether we are on the top edge of the global array */
329:   if (yinte == my) {
330:     j = my - 1;
331:     yinte = yinte - 1;
332:     /* top edge */
333:     for (i=xs; i<xs+xm; i++) {
334:         f[j][i].u     = x[j][i].u - lid;
335:         f[j][i].v     = x[j][i].v;
336:         f[j][i].omega = x[j][i].omega + (x[j][i].u - x[j-1][i].u)*dhy;
337:         f[j][i].temp  = x[j][i].temp-x[j-1][i].temp;
338:     }
339:   }

341:   /* Test whether we are on the left edge of the global array */
342:   if (xints == 0) {
343:     i = 0;
344:     xints = xints + 1;
345:     /* left edge */
346:     for (j=ys; j<ys+ym; j++) {
347:       f[j][i].u     = x[j][i].u;
348:       f[j][i].v     = x[j][i].v;
349:       f[j][i].omega = x[j][i].omega - (x[j][i+1].v - x[j][i].v)*dhx;
350:       f[j][i].temp  = x[j][i].temp;
351:     }
352:   }

354:   /* Test whether we are on the right edge of the global array */
355:   if (xinte == mx) {
356:     i = mx - 1;
357:     xinte = xinte - 1;
358:     /* right edge */
359:     for (j=ys; j<ys+ym; j++) {
360:       f[j][i].u     = x[j][i].u;
361:       f[j][i].v     = x[j][i].v;
362:       f[j][i].omega = x[j][i].omega - (x[j][i].v - x[j][i-1].v)*dhx;
363:       f[j][i].temp  = x[j][i].temp - (double)(grashof>0);
364:     }
365:   }

367:   /* Compute over the interior points */
368:   for (j=yints; j<yinte; j++) {
369:     for (i=xints; i<xinte; i++) {

371:         /*
372:           convective coefficients for upwinding
373:         */
374:         vx = x[j][i].u; avx = PetscAbsScalar(vx);
375:         vxp = p5*(vx+avx); vxm = p5*(vx-avx);
376:         vy = x[j][i].v; avy = PetscAbsScalar(vy);
377:         vyp = p5*(vy+avy); vym = p5*(vy-avy);

379:         /* U velocity */
380:         u          = x[j][i].u;
381:         uxx        = (two*u - x[j][i-1].u - x[j][i+1].u)*hydhx;
382:         uyy        = (two*u - x[j-1][i].u - x[j+1][i].u)*hxdhy;
383:         f[j][i].u  = uxx + uyy - p5*(x[j+1][i].omega-x[j-1][i].omega)*hx;

385:         /* V velocity */
386:         u          = x[j][i].v;
387:         uxx        = (two*u - x[j][i-1].v - x[j][i+1].v)*hydhx;
388:         uyy        = (two*u - x[j-1][i].v - x[j+1][i].v)*hxdhy;
389:         f[j][i].v  = uxx + uyy + p5*(x[j][i+1].omega-x[j][i-1].omega)*hy;

391:         /* Omega */
392:         u          = x[j][i].omega;
393:         uxx        = (two*u - x[j][i-1].omega - x[j][i+1].omega)*hydhx;
394:         uyy        = (two*u - x[j-1][i].omega - x[j+1][i].omega)*hxdhy;
395:         f[j][i].omega = uxx + uyy +
396:                         (vxp*(u - x[j][i-1].omega) +
397:                           vxm*(x[j][i+1].omega - u)) * hy +
398:                         (vyp*(u - x[j-1][i].omega) +
399:                           vym*(x[j+1][i].omega - u)) * hx -
400:                         p5 * grashof * (x[j][i+1].temp - x[j][i-1].temp) * hy;

402:         /* Temperature */
403:         u             = x[j][i].temp;
404:         uxx           = (two*u - x[j][i-1].temp - x[j][i+1].temp)*hydhx;
405:         uyy           = (two*u - x[j-1][i].temp - x[j+1][i].temp)*hxdhy;
406:         f[j][i].temp =  uxx + uyy  + prandtl * (
407:                         (vxp*(u - x[j][i-1].temp) +
408:                           vxm*(x[j][i+1].temp - u)) * hy +
409:                         (vyp*(u - x[j-1][i].temp) +
410:                                  vym*(x[j+1][i].temp - u)) * hx);
411:     }
412:   }

414:   /*
415:      Restore vectors
416:   */
417:   DAVecRestoreArray((DA)dmmg->dm,localX,(void**)&x);
418:   DAVecRestoreArray((DA)dmmg->dm,F,(void**)&f);

420:   DARestoreLocalVector((DA)dmmg->dm,&localX);

422:   /*
423:      Flop count (multiply-adds are counted as 2 operations)
424:   */
425:   PetscLogFlops(84*ym*xm);

427:   return 0;
428: }

430: int FormFunctionLocal(Field **x,Field **f,DALocalInfo *info,void *ptr)
431:  {
432:   AppCtx  *user = (AppCtx*)ptr;
433:   int     ierr,i,j;
434:   int     xints,xinte,yints,yinte;
435:   double  hx,hy,dhx,dhy,hxdhy,hydhx;
436:   double  grashof,prandtl,lid;
437:   Scalar  u,uxx,uyy,vx,vy,avx,avy,vxp,vxm,vyp,vym;

439:   grashof = user->grashof;
440:   prandtl = user->prandtl;
441:   lid     = user->lidvelocity;

443:   /* 
444:      Define mesh intervals ratios for uniform grid.
445:      [Note: FD formulae below are normalized by multiplying through by
446:      local volume element to obtain coefficients O(1) in two dimensions.]
447:   */
448:   dhx = (double)(info->mx-1);     dhy = (double)(info->my-1);
449:   hx = 1.0/dhx;                   hy = 1.0/dhy;
450:   hxdhy = hx*dhy;                 hydhx = hy*dhx;

452:   xints = info->xs; xinte = info->xs+info->xm; yints = info->ys; yinte = info->ys+info->ym;

454:   /* Test whether we are on the bottom edge of the global array */
455:   if (yints == 0) {
456:     j = 0;
457:     yints = yints + 1;
458:     /* bottom edge */
459:     for (i=info->xs; i<info->xs+info->xm; i++) {
460:         f[j][i].u     = x[j][i].u;
461:         f[j][i].v     = x[j][i].v;
462:         f[j][i].omega = x[j][i].omega + (x[j+1][i].u - x[j][i].u)*dhy;
463:         f[j][i].temp  = x[j][i].temp-x[j+1][i].temp;
464:     }
465:   }

467:   /* Test whether we are on the top edge of the global array */
468:   if (yinte == info->my) {
469:     j = info->my - 1;
470:     yinte = yinte - 1;
471:     /* top edge */
472:     for (i=info->xs; i<info->xs+info->xm; i++) {
473:         f[j][i].u     = x[j][i].u - lid;
474:         f[j][i].v     = x[j][i].v;
475:         f[j][i].omega = x[j][i].omega + (x[j][i].u - x[j-1][i].u)*dhy;
476:         f[j][i].temp  = x[j][i].temp-x[j-1][i].temp;
477:     }
478:   }

480:   /* Test whether we are on the left edge of the global array */
481:   if (xints == 0) {
482:     i = 0;
483:     xints = xints + 1;
484:     /* left edge */
485:     for (j=info->ys; j<info->ys+info->ym; j++) {
486:       f[j][i].u     = x[j][i].u;
487:       f[j][i].v     = x[j][i].v;
488:       f[j][i].omega = x[j][i].omega - (x[j][i+1].v - x[j][i].v)*dhx;
489:       f[j][i].temp  = x[j][i].temp;
490:     }
491:   }

493:   /* Test whether we are on the right edge of the global array */
494:   if (xinte == info->mx) {
495:     i = info->mx - 1;
496:     xinte = xinte - 1;
497:     /* right edge */
498:     for (j=info->ys; j<info->ys+info->ym; j++) {
499:       f[j][i].u     = x[j][i].u;
500:       f[j][i].v     = x[j][i].v;
501:       f[j][i].omega = x[j][i].omega - (x[j][i].v - x[j][i-1].v)*dhx;
502:       f[j][i].temp  = x[j][i].temp - (double)(grashof>0);
503:     }
504:   }

506:   /* Compute over the interior points */
507:   for (j=yints; j<yinte; j++) {
508:     for (i=xints; i<xinte; i++) {

510:         /*
511:           convective coefficients for upwinding
512:         */
513:         vx = x[j][i].u; avx = PetscAbsScalar(vx);
514:         vxp = .5*(vx+avx); vxm = .5*(vx-avx);
515:         vy = x[j][i].v; avy = PetscAbsScalar(vy);
516:         vyp = .5*(vy+avy); vym = .5*(vy-avy);

518:         /* U velocity */
519:         u          = x[j][i].u;
520:         uxx        = (2.0*u - x[j][i-1].u - x[j][i+1].u)*hydhx;
521:         uyy        = (2.0*u - x[j-1][i].u - x[j+1][i].u)*hxdhy;
522:         f[j][i].u  = uxx + uyy - .5*(x[j+1][i].omega-x[j-1][i].omega)*hx;

524:         /* V velocity */
525:         u          = x[j][i].v;
526:         uxx        = (2.0*u - x[j][i-1].v - x[j][i+1].v)*hydhx;
527:         uyy        = (2.0*u - x[j-1][i].v - x[j+1][i].v)*hxdhy;
528:         f[j][i].v  = uxx + uyy + .5*(x[j][i+1].omega-x[j][i-1].omega)*hy;

530:         /* Omega */
531:         u          = x[j][i].omega;
532:         uxx        = (2.0*u - x[j][i-1].omega - x[j][i+1].omega)*hydhx;
533:         uyy        = (2.0*u - x[j-1][i].omega - x[j+1][i].omega)*hxdhy;
534:         f[j][i].omega = uxx + uyy +
535:                         (vxp*(u - x[j][i-1].omega) +
536:                           vxm*(x[j][i+1].omega - u)) * hy +
537:                         (vyp*(u - x[j-1][i].omega) +
538:                           vym*(x[j+1][i].omega - u)) * hx -
539:                         .5 * grashof * (x[j][i+1].temp - x[j][i-1].temp) * hy;

541:         /* Temperature */
542:         u             = x[j][i].temp;
543:         uxx           = (2.0*u - x[j][i-1].temp - x[j][i+1].temp)*hydhx;
544:         uyy           = (2.0*u - x[j-1][i].temp - x[j+1][i].temp)*hxdhy;
545:         f[j][i].temp =  uxx + uyy  + prandtl * (
546:                         (vxp*(u - x[j][i-1].temp) +
547:                           vxm*(x[j][i+1].temp - u)) * hy +
548:                         (vyp*(u - x[j-1][i].temp) +
549:                                  vym*(x[j+1][i].temp - u)) * hx);
550:     }
551:   }

553:   /*
554:      Flop count (multiply-adds are counted as 2 operations)
555:   */
556:   PetscLogFlops(84*info->ym*info->xm);
557:   return 0;
558: }