Actual source code: mg.c
1: /*$Id: mg.c,v 1.128 2001/10/03 22:12:40 balay Exp $*/
2: /*
3: Defines the multigrid preconditioner interface.
4: */
5: #include src/sles/pc/impls/mg/mgimpl.h
8: /*
9: MGMCycle_Private - Given an MG structure created with MGCreate() runs
10: one multiplicative cycle down through the levels and
11: back up.
13: Input Parameter:
14: . mg - structure created with MGCreate().
15: */
16: int MGMCycle_Private(MG *mglevels,PetscTruth *converged)
17: {
18: MG mg = *mglevels,mgc;
19: int cycles = mg->cycles,ierr,its;
20: PetscScalar zero = 0.0;
23: if (converged) *converged = PETSC_FALSE;
25: if (!mg->level) { /* coarse grid */
26: SLESSolve(mg->smoothd,mg->b,mg->x,&its);
27: } else {
28: SLESSolve(mg->smoothd,mg->b,mg->x,&its);
29: (*mg->residual)(mg->A,mg->b,mg->x,mg->r);
31: /* if on finest level and have convergence criteria set */
32: if (mg->level == mg->levels-1 && mg->ttol) {
33: PetscReal rnorm;
34: VecNorm(mg->r,NORM_2,&rnorm);
35: if (rnorm <= mg->ttol) {
36: *converged = PETSC_TRUE;
37: if (rnorm < mg->atol) {
38: PetscLogInfo(0,"Linear solver has converged. Residual norm %g is less than absolute tolerance %gn",rnorm,mg->atol);
39: } else {
40: PetscLogInfo(0,"Linear solver has converged. Residual norm %g is less than relative tolerance times initial residual norm %gn",rnorm,mg->ttol);
41: }
42: return(0);
43: }
44: }
46: mgc = *(mglevels - 1);
47: MatRestrict(mg->restrct,mg->r,mgc->b);
48: VecSet(&zero,mgc->x);
49: while (cycles--) {
50: MGMCycle_Private(mglevels-1,converged);
51: }
52: MatInterpolateAdd(mg->interpolate,mgc->x,mg->x,mg->x);
53: SLESSolve(mg->smoothu,mg->b,mg->x,&its);
54: }
55: return(0);
56: }
58: /*
59: MGCreate_Private - Creates a MG structure for use with the
60: multigrid code. Level 0 is the coarsest. (But the
61: finest level is stored first in the array).
63: */
64: static int MGCreate_Private(MPI_Comm comm,int levels,PC pc,MPI_Comm *comms,MG **result)
65: {
66: MG *mg;
67: int i,ierr,size;
68: char *prefix;
69: KSP ksp;
70: PC ipc;
73: PetscMalloc(levels*sizeof(MG),&mg);
74: PetscLogObjectMemory(pc,levels*(sizeof(MG)+sizeof(struct _MG)));
76: PCGetOptionsPrefix(pc,&prefix);
78: for (i=0; i<levels; i++) {
79: PetscNew(struct _MG,&mg[i]);
80: PetscMemzero(mg[i],sizeof(struct _MG));
81: mg[i]->level = i;
82: mg[i]->levels = levels;
83: mg[i]->cycles = 1;
85: if (comms) comm = comms[i];
86: SLESCreate(comm,&mg[i]->smoothd);
87: SLESGetKSP(mg[i]->smoothd,&ksp);
88: KSPSetTolerances(ksp,PETSC_DEFAULT,PETSC_DEFAULT,PETSC_DEFAULT,1);
89: SLESSetOptionsPrefix(mg[i]->smoothd,prefix);
91: /* do special stuff for coarse grid */
92: if (!i && levels > 1) {
93: SLESAppendOptionsPrefix(mg[0]->smoothd,"mg_coarse_");
95: /* coarse solve is (redundant) LU by default */
96: SLESGetKSP(mg[0]->smoothd,&ksp);
97: KSPSetType(ksp,KSPPREONLY);
98: SLESGetPC(mg[0]->smoothd,&ipc);
99: MPI_Comm_size(comm,&size);
100: if (size > 1) {
101: PCSetType(ipc,PCREDUNDANT);
102: PCRedundantGetPC(ipc,&ipc);
103: }
104: PCSetType(ipc,PCLU);
106: } else {
107: SLESAppendOptionsPrefix(mg[i]->smoothd,"mg_levels_");
108: }
109: PetscLogObjectParent(pc,mg[i]->smoothd);
110: mg[i]->smoothu = mg[i]->smoothd;
111: mg[i]->default_smoothu = 10000;
112: mg[i]->default_smoothd = 10000;
113: mg[i]->rtol = 0.0;
114: mg[i]->atol = 0.0;
115: mg[i]->dtol = 0.0;
116: mg[i]->ttol = 0.0;
117: }
118: *result = mg;
119: return(0);
120: }
122: static int PCDestroy_MG(PC pc)
123: {
124: MG *mg = (MG*)pc->data;
125: int i,n = mg[0]->levels,ierr;
128: for (i=0; i<n; i++) {
129: if (mg[i]->smoothd != mg[i]->smoothu) {
130: SLESDestroy(mg[i]->smoothd);
131: }
132: SLESDestroy(mg[i]->smoothu);
133: PetscFree(mg[i]);
134: }
135: PetscFree(mg);
136: return(0);
137: }
141: EXTERN int MGACycle_Private(MG*);
142: EXTERN int MGFCycle_Private(MG*);
143: EXTERN int MGKCycle_Private(MG*);
145: /*
146: PCApply_MG - Runs either an additive, multiplicative, Kaskadic
147: or full cycle of multigrid.
149: Note:
150: A simple wrapper which calls MGMCycle(),MGACycle(), or MGFCycle().
151: */
152: static int PCApply_MG(PC pc,Vec b,Vec x)
153: {
154: MG *mg = (MG*)pc->data;
155: PetscScalar zero = 0.0;
156: int levels = mg[0]->levels,ierr;
159: mg[levels-1]->b = b;
160: mg[levels-1]->x = x;
161: if (mg[0]->am == MGMULTIPLICATIVE) {
162: VecSet(&zero,x);
163: MGMCycle_Private(mg+levels-1,PETSC_NULL);
164: }
165: else if (mg[0]->am == MGADDITIVE) {
166: MGACycle_Private(mg);
167: }
168: else if (mg[0]->am == MGKASKADE) {
169: MGKCycle_Private(mg);
170: }
171: else {
172: MGFCycle_Private(mg);
173: }
174: return(0);
175: }
177: static int PCApplyRichardson_MG(PC pc,Vec b,Vec x,Vec w,PetscReal rtol,PetscReal atol, PetscReal dtol,int its)
178: {
179: MG *mg = (MG*)pc->data;
180: int ierr,levels = mg[0]->levels;
181: PetscTruth converged = PETSC_FALSE;
184: mg[levels-1]->b = b;
185: mg[levels-1]->x = x;
187: mg[levels-1]->rtol = rtol;
188: mg[levels-1]->atol = atol;
189: mg[levels-1]->dtol = dtol;
190: if (rtol) {
191: /* compute initial residual norm for relative convergence test */
192: PetscReal rnorm;
193: ierr = (*mg[levels-1]->residual)(mg[levels-1]->A,b,x,w);
194: ierr = VecNorm(w,NORM_2,&rnorm);
195: mg[levels-1]->ttol = PetscMax(rtol*rnorm,atol);
196: } else if (atol) {
197: mg[levels-1]->ttol = atol;
198: } else {
199: mg[levels-1]->ttol = 0.0;
200: }
202: while (its-- && !converged) {
203: MGMCycle_Private(mg+levels-1,&converged);
204: }
205: return(0);
206: }
208: static int PCSetFromOptions_MG(PC pc)
209: {
210: int ierr,m,levels = 1;
211: PetscTruth flg;
212: char buff[16],*type[] = {"additive","multiplicative","full","cascade"};
216: PetscOptionsHead("Multigrid options");
217: if (!pc->data) {
218: PetscOptionsInt("-pc_mg_levels","Number of Levels","MGSetLevels",levels,&levels,&flg);
219: MGSetLevels(pc,levels,PETSC_NULL);
220: }
221: PetscOptionsInt("-pc_mg_cycles","1 for V cycle, 2 for W-cycle","MGSetCycles",1,&m,&flg);
222: if (flg) {
223: MGSetCycles(pc,m);
224: }
225: PetscOptionsInt("-pc_mg_smoothup","Number of post-smoothing steps","MGSetNumberSmoothUp",1,&m,&flg);
226: if (flg) {
227: MGSetNumberSmoothUp(pc,m);
228: }
229: PetscOptionsInt("-pc_mg_smoothdown","Number of pre-smoothing steps","MGSetNumberSmoothDown",1,&m,&flg);
230: if (flg) {
231: MGSetNumberSmoothDown(pc,m);
232: }
233: PetscOptionsEList("-pc_mg_type","Multigrid type","MGSetType",type,4,"multiplicative",buff,15,&flg);
234: if (flg) {
235: MGType mg;
236: PetscTruth isadd,ismult,isfull,iskask,iscasc;
238: PetscStrcmp(buff,type[0],&isadd);
239: PetscStrcmp(buff,type[1],&ismult);
240: PetscStrcmp(buff,type[2],&isfull);
241: PetscStrcmp(buff,type[3],&iscasc);
242: PetscStrcmp(buff,"kaskade",&iskask);
244: if (isadd) mg = MGADDITIVE;
245: else if (ismult) mg = MGMULTIPLICATIVE;
246: else if (isfull) mg = MGFULL;
247: else if (iskask) mg = MGKASKADE;
248: else if (iscasc) mg = MGKASKADE;
249: else SETERRQ1(PETSC_ERR_ARG_OUTOFRANGE,"Unknown type: %s",buff);
250: MGSetType(pc,mg);
251: }
252: PetscOptionsTail();
253: return(0);
254: }
256: static int PCView_MG(PC pc,PetscViewer viewer)
257: {
258: MG *mg = (MG*)pc->data;
259: KSP kspu,kspd;
260: int itu,itd,ierr,levels = mg[0]->levels,i;
261: PetscReal dtol,atol,rtol;
262: char *cstring;
263: PetscTruth isascii;
266: PetscTypeCompare((PetscObject)viewer,PETSC_VIEWER_ASCII,&isascii);
267: if (isascii) {
268: SLESGetKSP(mg[0]->smoothu,&kspu);
269: SLESGetKSP(mg[0]->smoothd,&kspd);
270: KSPGetTolerances(kspu,&dtol,&atol,&rtol,&itu);
271: KSPGetTolerances(kspd,&dtol,&atol,&rtol,&itd);
272: if (mg[0]->am == MGMULTIPLICATIVE) cstring = "multiplicative";
273: else if (mg[0]->am == MGADDITIVE) cstring = "additive";
274: else if (mg[0]->am == MGFULL) cstring = "full";
275: else if (mg[0]->am == MGKASKADE) cstring = "Kaskade";
276: else cstring = "unknown";
277: PetscViewerASCIIPrintf(viewer," MG: type is %s, levels=%d cycles=%d, pre-smooths=%d, post-smooths=%dn",
278: cstring,levels,mg[0]->cycles,mg[0]->default_smoothu,mg[0]->default_smoothd);
279: for (i=0; i<levels; i++) {
280: PetscViewerASCIIPrintf(viewer,"Down solver (pre-smoother) on level %d -------------------------------n",i);
281: PetscViewerASCIIPushTab(viewer);
282: SLESView(mg[i]->smoothd,viewer);
283: PetscViewerASCIIPopTab(viewer);
284: if (mg[i]->smoothd == mg[i]->smoothu) {
285: PetscViewerASCIIPrintf(viewer,"Up solver same as down solvern");
286: } else {
287: PetscViewerASCIIPrintf(viewer,"Up solver (post-smoother) on level %d -------------------------------n",i);
288: PetscViewerASCIIPushTab(viewer);
289: SLESView(mg[i]->smoothu,viewer);
290: PetscViewerASCIIPopTab(viewer);
291: }
292: }
293: } else {
294: SETERRQ1(1,"Viewer type %s not supported for PCMG",((PetscObject)viewer)->type_name);
295: }
296: return(0);
297: }
299: /*
300: Calls setup for the SLES on each level
301: */
302: static int PCSetUp_MG(PC pc)
303: {
304: MG *mg = (MG*)pc->data;
305: int ierr,i,n = mg[0]->levels;
306: KSP ksp;
307: PC cpc;
308: PetscTruth preonly,lu,redundant,monitor = PETSC_FALSE,dump;
309: PetscViewer ascii;
310: MPI_Comm comm;
313: /*
314: temporarily stick pc->vec into mg[0]->b and x so that
315: SLESSetUp is happy. Since currently those slots are empty.
316: */
317: mg[n-1]->x = pc->vec;
318: mg[n-1]->b = pc->vec;
320: if (pc->setupcalled == 0) {
321: PetscOptionsHasName(0,"-pc_mg_monitor",&monitor);
322:
323: for (i=1; i<n; i++) {
324: if (mg[i]->smoothd) {
325: if (monitor) {
326: SLESGetKSP(mg[i]->smoothd,&ksp);
327: PetscObjectGetComm((PetscObject)ksp,&comm);
328: PetscViewerASCIIOpen(comm,"stdout",&ascii);
329: PetscViewerASCIISetTab(ascii,n-i);
330: KSPSetMonitor(ksp,KSPDefaultMonitor,ascii,(int(*)(void*))PetscViewerDestroy);
331: }
332: SLESSetFromOptions(mg[i]->smoothd);
333: }
334: }
335: for (i=0; i<n; i++) {
336: if (mg[i]->smoothu && mg[i]->smoothu != mg[i]->smoothd) {
337: if (monitor) {
338: SLESGetKSP(mg[i]->smoothu,&ksp);
339: PetscObjectGetComm((PetscObject)ksp,&comm);
340: PetscViewerASCIIOpen(comm,"stdout",&ascii);
341: PetscViewerASCIISetTab(ascii,n-i);
342: KSPSetMonitor(ksp,KSPDefaultMonitor,ascii,(int(*)(void*))PetscViewerDestroy);
343: }
344: SLESSetFromOptions(mg[i]->smoothu);
345: }
346: }
347: }
349: for (i=1; i<n; i++) {
350: if (mg[i]->smoothd) {
351: SLESGetKSP(mg[i]->smoothd,&ksp);
352: KSPSetInitialGuessNonzero(ksp,PETSC_TRUE);
353: SLESSetUp(mg[i]->smoothd,mg[i]->b,mg[i]->x);
354: }
355: }
356: for (i=0; i<n; i++) {
357: if (mg[i]->smoothu && mg[i]->smoothu != mg[i]->smoothd) {
358: SLESGetKSP(mg[i]->smoothu,&ksp);
359: KSPSetInitialGuessNonzero(ksp,PETSC_TRUE);
360: SLESSetUp(mg[i]->smoothu,mg[i]->b,mg[i]->x);
361: }
362: }
364: /*
365: If coarse solver is not direct method then DO NOT USE preonly
366: */
367: SLESGetKSP(mg[0]->smoothd,&ksp);
368: PetscTypeCompare((PetscObject)ksp,KSPPREONLY,&preonly);
369: if (preonly) {
370: SLESGetPC(mg[0]->smoothd,&cpc);
371: PetscTypeCompare((PetscObject)cpc,PCLU,&lu);
372: PetscTypeCompare((PetscObject)cpc,PCREDUNDANT,&redundant);
373: if (!lu && !redundant) {
374: KSPSetType(ksp,KSPGMRES);
375: }
376: }
378: if (pc->setupcalled == 0) {
379: if (monitor) {
380: SLESGetKSP(mg[0]->smoothd,&ksp);
381: PetscObjectGetComm((PetscObject)ksp,&comm);
382: PetscViewerASCIIOpen(comm,"stdout",&ascii);
383: PetscViewerASCIISetTab(ascii,n);
384: KSPSetMonitor(ksp,KSPDefaultMonitor,ascii,(int(*)(void*))PetscViewerDestroy);
385: }
386: SLESSetFromOptions(mg[0]->smoothd);
387: }
389: SLESSetUp(mg[0]->smoothd,mg[0]->b,mg[0]->x);
391: /*
392: Dump the interpolation/restriction matrices to matlab plus the
393: Jacobian/stiffness on each level. This allows Matlab users to
394: easily check if the Galerkin condition A_c = R A_f R^T is satisfied */
395: PetscOptionsHasName(pc->prefix,"-pc_mg_dump_matlab",&dump);
396: if (dump) {
397: for (i=1; i<n; i++) {
398: MatView(mg[i]->restrct,PETSC_VIEWER_SOCKET_(pc->comm));
399: }
400: for (i=0; i<n; i++) {
401: SLESGetPC(mg[i]->smoothd,&pc);
402: MatView(pc->mat,PETSC_VIEWER_SOCKET_(pc->comm));
403: }
404: }
406: return(0);
407: }
409: /* -------------------------------------------------------------------------------------*/
411: /*@C
412: MGSetLevels - Sets the number of levels to use with MG.
413: Must be called before any other MG routine.
415: Collective on PC
417: Input Parameters:
418: + pc - the preconditioner context
419: . levels - the number of levels
420: - comms - optional communicators for each level; this is to allow solving the coarser problems
421: on smaller sets of processors. Use PETSC_NULL_OBJECT for default in Fortran
423: Level: intermediate
425: Notes:
426: If the number of levels is one then the multigrid uses the -mg_levels prefix
427: for setting the level options rather than the -mg_coarse prefix.
429: .keywords: MG, set, levels, multigrid
431: .seealso: MGSetType(), MGGetLevels()
432: @*/
433: int MGSetLevels(PC pc,int levels,MPI_Comm *comms)
434: {
436: MG *mg;
441: if (pc->data) {
442: SETERRQ(1,"Number levels already set for MGn
443: make sure that you call MGSetLevels() before SLESSetFromOptions()");
444: }
445: ierr = MGCreate_Private(pc->comm,levels,pc,comms,&mg);
446: mg[0]->am = MGMULTIPLICATIVE;
447: pc->data = (void*)mg;
448: pc->ops->applyrichardson = PCApplyRichardson_MG;
449: return(0);
450: }
452: /*@
453: MGGetLevels - Gets the number of levels to use with MG.
455: Not Collective
457: Input Parameter:
458: . pc - the preconditioner context
460: Output parameter:
461: . levels - the number of levels
463: Level: advanced
465: .keywords: MG, get, levels, multigrid
467: .seealso: MGSetLevels()
468: @*/
469: int MGGetLevels(PC pc,int *levels)
470: {
471: MG *mg;
476: mg = (MG*)pc->data;
477: *levels = mg[0]->levels;
478: return(0);
479: }
481: /*@
482: MGSetType - Determines the form of multigrid to use:
483: multiplicative, additive, full, or the Kaskade algorithm.
485: Collective on PC
487: Input Parameters:
488: + pc - the preconditioner context
489: - form - multigrid form, one of MGMULTIPLICATIVE, MGADDITIVE,
490: MGFULL, MGKASKADE
492: Options Database Key:
493: . -pc_mg_type <form> - Sets <form>, one of multiplicative,
494: additive, full, kaskade
496: Level: advanced
498: .keywords: MG, set, method, multiplicative, additive, full, Kaskade, multigrid
500: .seealso: MGSetLevels()
501: @*/
502: int MGSetType(PC pc,MGType form)
503: {
504: MG *mg;
508: mg = (MG*)pc->data;
510: if (!mg) SETERRQ(PETSC_ERR_ARG_WRONGSTATE,"Must set MG levels before calling");
511: mg[0]->am = form;
512: if (form == MGMULTIPLICATIVE) pc->ops->applyrichardson = PCApplyRichardson_MG;
513: else pc->ops->applyrichardson = 0;
514: return(0);
515: }
517: /*@
518: MGSetCycles - Sets the type cycles to use. Use MGSetCyclesOnLevel() for more
519: complicated cycling.
521: Collective on PC
523: Input Parameters:
524: + mg - the multigrid context
525: - n - the number of cycles
527: Options Database Key:
528: $ -pc_mg_cycles n - 1 denotes a V-cycle; 2 denotes a W-cycle.
530: Level: advanced
532: .keywords: MG, set, cycles, V-cycle, W-cycle, multigrid
534: .seealso: MGSetCyclesOnLevel()
535: @*/
536: int MGSetCycles(PC pc,int n)
537: {
538: MG *mg;
539: int i,levels;
543: mg = (MG*)pc->data;
544: if (!mg) SETERRQ(PETSC_ERR_ARG_WRONGSTATE,"Must set MG levels before calling");
545: levels = mg[0]->levels;
547: for (i=0; i<levels; i++) {
548: mg[i]->cycles = n;
549: }
550: return(0);
551: }
553: /*@
554: MGCheck - Checks that all components of the MG structure have
555: been set.
557: Collective on PC
559: Input Parameters:
560: . mg - the MG structure
562: Level: advanced
564: .keywords: MG, check, set, multigrid
565: @*/
566: int MGCheck(PC pc)
567: {
568: MG *mg;
569: int i,n,count = 0;
573: mg = (MG*)pc->data;
575: if (!mg) SETERRQ(PETSC_ERR_ARG_WRONGSTATE,"Must set MG levels before calling");
577: n = mg[0]->levels;
579: for (i=1; i<n; i++) {
580: if (!mg[i]->restrct) {
581: (*PetscErrorPrintf)("No restrict set level %d n",n-i); count++;
582: }
583: if (!mg[i]->interpolate) {
584: (*PetscErrorPrintf)("No interpolate set level %d n",n-i); count++;
585: }
586: if (!mg[i]->residual) {
587: (*PetscErrorPrintf)("No residual set level %d n",n-i); count++;
588: }
589: if (!mg[i]->smoothu) {
590: (*PetscErrorPrintf)("No smoothup set level %d n",n-i); count++;
591: }
592: if (!mg[i]->smoothd) {
593: (*PetscErrorPrintf)("No smoothdown set level %d n",n-i); count++;
594: }
595: if (!mg[i]->r) {
596: (*PetscErrorPrintf)("No r set level %d n",n-i); count++;
597: }
598: if (!mg[i-1]->x) {
599: (*PetscErrorPrintf)("No x set level %d n",n-i); count++;
600: }
601: if (!mg[i-1]->b) {
602: (*PetscErrorPrintf)("No b set level %d n",n-i); count++;
603: }
604: }
605: PetscFunctionReturn(count);
606: }
609: /*@
610: MGSetNumberSmoothDown - Sets the number of pre-smoothing steps to
611: use on all levels. Use MGGetSmootherDown() to set different
612: pre-smoothing steps on different levels.
614: Collective on PC
616: Input Parameters:
617: + mg - the multigrid context
618: - n - the number of smoothing steps
620: Options Database Key:
621: . -pc_mg_smoothdown <n> - Sets number of pre-smoothing steps
623: Level: advanced
625: .keywords: MG, smooth, down, pre-smoothing, steps, multigrid
627: .seealso: MGSetNumberSmoothUp()
628: @*/
629: int MGSetNumberSmoothDown(PC pc,int n)
630: {
631: MG *mg;
632: int i,levels,ierr;
633: KSP ksp;
637: mg = (MG*)pc->data;
638: if (!mg) SETERRQ(PETSC_ERR_ARG_WRONGSTATE,"Must set MG levels before calling");
639: levels = mg[0]->levels;
641: for (i=0; i<levels; i++) {
642: SLESGetKSP(mg[i]->smoothd,&ksp);
643: KSPSetTolerances(ksp,PETSC_DEFAULT,PETSC_DEFAULT,PETSC_DEFAULT,n);
644: mg[i]->default_smoothd = n;
645: }
646: return(0);
647: }
649: /*@
650: MGSetNumberSmoothUp - Sets the number of post-smoothing steps to use
651: on all levels. Use MGGetSmootherUp() to set different numbers of
652: post-smoothing steps on different levels.
654: Collective on PC
656: Input Parameters:
657: + mg - the multigrid context
658: - n - the number of smoothing steps
660: Options Database Key:
661: . -pc_mg_smoothup <n> - Sets number of post-smoothing steps
663: Level: advanced
665: .keywords: MG, smooth, up, post-smoothing, steps, multigrid
667: .seealso: MGSetNumberSmoothDown()
668: @*/
669: int MGSetNumberSmoothUp(PC pc,int n)
670: {
671: MG *mg;
672: int i,levels,ierr;
673: KSP ksp;
677: mg = (MG*)pc->data;
678: if (!mg) SETERRQ(PETSC_ERR_ARG_WRONGSTATE,"Must set MG levels before calling");
679: levels = mg[0]->levels;
681: for (i=0; i<levels; i++) {
682: SLESGetKSP(mg[i]->smoothu,&ksp);
683: KSPSetTolerances(ksp,PETSC_DEFAULT,PETSC_DEFAULT,PETSC_DEFAULT,n);
684: mg[i]->default_smoothu = n;
685: }
686: return(0);
687: }
689: /* ----------------------------------------------------------------------------------------*/
691: EXTERN_C_BEGIN
692: int PCCreate_MG(PC pc)
693: {
695: pc->ops->apply = PCApply_MG;
696: pc->ops->setup = PCSetUp_MG;
697: pc->ops->destroy = PCDestroy_MG;
698: pc->ops->setfromoptions = PCSetFromOptions_MG;
699: pc->ops->view = PCView_MG;
701: pc->data = (void*)0;
702: return(0);
703: }
704: EXTERN_C_END