Actual source code: pgmres.c

  1: /*
  2:     This file implements PGMRES (a Pipelined Generalized Minimal Residual method)
  3: */

  5: #include <../src/ksp/ksp/impls/gmres/pgmres/pgmresimpl.h>

  7: static PetscErrorCode KSPPGMRESUpdateHessenberg(KSP, PetscInt, PetscBool *, PetscReal *);
  8: static PetscErrorCode KSPPGMRESBuildSoln(PetscScalar *, Vec, Vec, KSP, PetscInt);

 10: static PetscErrorCode KSPSetUp_PGMRES(KSP ksp)
 11: {
 12:   PetscFunctionBegin;
 13:   PetscCall(KSPSetUp_GMRES(ksp));
 14:   PetscFunctionReturn(PETSC_SUCCESS);
 15: }

 17: static PetscErrorCode KSPPGMRESCycle(PetscInt *itcount, KSP ksp)
 18: {
 19:   KSP_PGMRES *pgmres = (KSP_PGMRES *)ksp->data;
 20:   PetscReal   res_norm, res, newnorm;
 21:   PetscInt    it     = 0, j, k;
 22:   PetscBool   hapend = PETSC_FALSE;

 24:   PetscFunctionBegin;
 25:   if (itcount) *itcount = 0;
 26:   PetscCall(VecNormalize(VEC_VV(0), &res_norm));
 27:   KSPCheckNorm(ksp, res_norm);
 28:   res    = res_norm;
 29:   *RS(0) = res_norm;

 31:   /* check for the convergence */
 32:   PetscCall(PetscObjectSAWsTakeAccess((PetscObject)ksp));
 33:   if (ksp->normtype != KSP_NORM_NONE) ksp->rnorm = res;
 34:   else ksp->rnorm = 0;
 35:   PetscCall(PetscObjectSAWsGrantAccess((PetscObject)ksp));
 36:   pgmres->it = it - 2;
 37:   PetscCall(KSPLogResidualHistory(ksp, ksp->rnorm));
 38:   PetscCall(KSPMonitor(ksp, ksp->its, ksp->rnorm));
 39:   if (!res) {
 40:     ksp->reason = KSP_CONVERGED_ATOL;
 41:     PetscCall(PetscInfo(ksp, "Converged due to zero residual norm on entry\n"));
 42:     PetscFunctionReturn(PETSC_SUCCESS);
 43:   }

 45:   PetscCall((*ksp->converged)(ksp, ksp->its, ksp->rnorm, &ksp->reason, ksp->cnvP));
 46:   for (; !ksp->reason; it++) {
 47:     Vec Zcur, Znext;
 48:     if (pgmres->vv_allocated <= it + VEC_OFFSET + 1) PetscCall(KSPGMRESGetNewVectors(ksp, it + 1));
 49:     /* VEC_VV(it-1) is orthogonal, it will be normalized once the VecNorm arrives. */
 50:     Zcur  = VEC_VV(it);     /* Zcur is not yet orthogonal, but the VecMDot to orthogonalize it has been started. */
 51:     Znext = VEC_VV(it + 1); /* This iteration will compute Znext, update with a deferred correction once we know how
 52:                                Zcur relates to the previous vectors, and start the reduction to orthogonalize it. */

 54:     if (it < pgmres->max_k + 1 && ksp->its + 1 < PetscMax(2, ksp->max_it)) { /* We don't know whether what we have computed is enough, so apply the matrix. */
 55:       PetscCall(KSP_PCApplyBAorAB(ksp, Zcur, Znext, VEC_TEMP_MATOP));
 56:     }

 58:     if (it > 1) { /* Complete the pending reduction */
 59:       PetscCall(VecNormEnd(VEC_VV(it - 1), NORM_2, &newnorm));
 60:       *HH(it - 1, it - 2) = newnorm;
 61:     }
 62:     if (it > 0) { /* Finish the reduction computing the latest column of H */
 63:       PetscCall(VecMDotEnd(Zcur, it, &(VEC_VV(0)), HH(0, it - 1)));
 64:     }

 66:     if (it > 1) {
 67:       /* normalize the base vector from two iterations ago, basis is complete up to here */
 68:       PetscCall(VecScale(VEC_VV(it - 1), 1. / *HH(it - 1, it - 2)));

 70:       PetscCall(KSPPGMRESUpdateHessenberg(ksp, it - 2, &hapend, &res));
 71:       pgmres->it = it - 2;
 72:       ksp->its++;
 73:       if (ksp->normtype != KSP_NORM_NONE) ksp->rnorm = res;
 74:       else ksp->rnorm = 0;

 76:       PetscCall((*ksp->converged)(ksp, ksp->its, ksp->rnorm, &ksp->reason, ksp->cnvP));
 77:       if (ksp->reason) break;
 78:       if (it < pgmres->max_k + 1) { /* Monitor if we are not done or still iterating, but not before a restart. */
 79:         PetscCall(KSPLogResidualHistory(ksp, ksp->rnorm));
 80:         PetscCall(KSPMonitor(ksp, ksp->its, ksp->rnorm));
 81:       }
 82:       /* Catch error in happy breakdown and signal convergence and break from loop */
 83:       if (hapend) {
 84:         PetscCheck(!ksp->errorifnotconverged, PetscObjectComm((PetscObject)ksp), PETSC_ERR_NOT_CONVERGED, "Reached happy break down, but convergence was not indicated. Residual norm = %g", (double)res);
 85:         ksp->reason = KSP_DIVERGED_BREAKDOWN;
 86:         break;
 87:       }

 89:       if (!(it < pgmres->max_k + 1 && ksp->its < ksp->max_it)) break;

 91:       /* The it-2 column of H was not scaled when we computed Zcur, apply correction */
 92:       PetscCall(VecScale(Zcur, 1. / *HH(it - 1, it - 2)));
 93:       /* And Znext computed in this iteration was computed using the under-scaled Zcur */
 94:       PetscCall(VecScale(Znext, 1. / *HH(it - 1, it - 2)));

 96:       /* In the previous iteration, we projected an unnormalized Zcur against the Krylov basis, so we need to fix the column of H resulting from that projection. */
 97:       for (k = 0; k < it; k++) *HH(k, it - 1) /= *HH(it - 1, it - 2);
 98:       /* When Zcur was projected against the Krylov basis, VV(it-1) was still not normalized, so fix that too. This
 99:        * column is complete except for HH(it,it-1) which we won't know until the next iteration. */
100:       *HH(it - 1, it - 1) /= *HH(it - 1, it - 2);
101:     }

103:     if (it > 0) {
104:       PetscScalar *work;
105:       if (ksp->lorthogwork < pgmres->max_k + 2) {
106:         PetscCall(PetscFree(ksp->orthogwork));
107:         ksp->lorthogwork = pgmres->max_k + 2;
108:         PetscCall(PetscMalloc1(ksp->lorthogwork, &ksp->orthogwork));
109:       }
110:       work = ksp->orthogwork;
111:       /* Apply correction computed by the VecMDot in the last iteration to Znext. The original form is
112:        *
113:        *   Znext -= sum_{j=0}^{i-1} Z[j+1] * H[j,i-1]
114:        *
115:        * where
116:        *
117:        *   Z[j] = sum_{k=0}^j V[k] * H[k,j-1]
118:        *
119:        * substituting
120:        *
121:        *   Znext -= sum_{j=0}^{i-1} sum_{k=0}^{j+1} V[k] * H[k,j] * H[j,i-1]
122:        *
123:        * rearranging the iteration space from row-column to column-row
124:        *
125:        *   Znext -= sum_{k=0}^i sum_{j=k-1}^{i-1} V[k] * H[k,j] * H[j,i-1]
126:        *
127:        * Note that column it-1 of HH is correct. For all previous columns, we must look at HES because HH has already
128:        * been transformed to upper triangular form.
129:        */
130:       for (k = 0; k < it + 1; k++) {
131:         work[k] = 0;
132:         for (j = PetscMax(0, k - 1); j < it - 1; j++) work[k] -= *HES(k, j) * *HH(j, it - 1);
133:       }
134:       PetscCall(VecMAXPY(Znext, it + 1, work, &VEC_VV(0)));
135:       PetscCall(VecAXPY(Znext, -*HH(it - 1, it - 1), Zcur));

137:       /* Orthogonalize Zcur against existing basis vectors. */
138:       for (k = 0; k < it; k++) work[k] = -*HH(k, it - 1);
139:       PetscCall(VecMAXPY(Zcur, it, work, &VEC_VV(0)));
140:       /* Zcur is now orthogonal, and will be referred to as VEC_VV(it) again, though it is still not normalized. */
141:       /* Begin computing the norm of the new vector, will be normalized after the MatMult in the next iteration. */
142:       PetscCall(VecNormBegin(VEC_VV(it), NORM_2, &newnorm));
143:     }

145:     /* Compute column of H (to the diagonal, but not the subdiagonal) to be able to orthogonalize the newest vector. */
146:     PetscCall(VecMDotBegin(Znext, it + 1, &VEC_VV(0), HH(0, it)));

148:     /* Start an asynchronous split-mode reduction, the result of the MDot and Norm will be collected on the next iteration. */
149:     PetscCall(PetscCommSplitReductionBegin(PetscObjectComm((PetscObject)Znext)));
150:   }
151:   if (itcount) *itcount = it - 1; /* Number of iterations actually completed. */

153:   /*
154:     Solve for the "best" coefficients of the Krylov
155:     columns, add the solution values together, and possibly unwind the preconditioning from the solution
156:    */
157:   /* Form the solution (or the solution so far) */
158:   PetscCall(KSPPGMRESBuildSoln(RS(0), ksp->vec_sol, ksp->vec_sol, ksp, it - 2));

160:   if (ksp->reason == KSP_CONVERGED_ITERATING && ksp->its == ksp->max_it) ksp->reason = KSP_DIVERGED_ITS;
161:   if (ksp->reason) {
162:     PetscCall(KSPLogResidualHistory(ksp, ksp->rnorm));
163:     PetscCall(KSPMonitor(ksp, ksp->its, ksp->rnorm));
164:   }
165:   PetscFunctionReturn(PETSC_SUCCESS);
166: }

168: static PetscErrorCode KSPSolve_PGMRES(KSP ksp)
169: {
170:   PetscInt    its, itcount;
171:   KSP_PGMRES *pgmres     = (KSP_PGMRES *)ksp->data;
172:   PetscBool   guess_zero = ksp->guess_zero;

174:   PetscFunctionBegin;
175:   PetscCheck(!ksp->calc_sings || pgmres->Rsvd, PetscObjectComm((PetscObject)ksp), PETSC_ERR_ORDER, "Must call KSPSetComputeSingularValues() before KSPSetUp() is called");
176:   PetscCall(PetscObjectSAWsTakeAccess((PetscObject)ksp));
177:   ksp->its = 0;
178:   PetscCall(PetscObjectSAWsGrantAccess((PetscObject)ksp));

180:   itcount     = 0;
181:   ksp->reason = KSP_CONVERGED_ITERATING;
182:   while (!ksp->reason) {
183:     PetscCall(KSPInitialResidual(ksp, ksp->vec_sol, VEC_TEMP, VEC_TEMP_MATOP, VEC_VV(0), ksp->vec_rhs));
184:     PetscCall(KSPPGMRESCycle(&its, ksp));
185:     itcount += its;
186:     if (itcount >= ksp->max_it) {
187:       if (!ksp->reason) ksp->reason = KSP_DIVERGED_ITS;
188:       break;
189:     }
190:     ksp->guess_zero = PETSC_FALSE; /* every future call to KSPInitialResidual() will have nonzero guess */
191:   }
192:   ksp->guess_zero = guess_zero; /* restore if user provided nonzero initial guess */
193:   PetscFunctionReturn(PETSC_SUCCESS);
194: }

196: static PetscErrorCode KSPDestroy_PGMRES(KSP ksp)
197: {
198:   PetscFunctionBegin;
199:   PetscCall(KSPDestroy_GMRES(ksp));
200:   PetscFunctionReturn(PETSC_SUCCESS);
201: }

203: static PetscErrorCode KSPPGMRESBuildSoln(PetscScalar *nrs, Vec vguess, Vec vdest, KSP ksp, PetscInt it)
204: {
205:   PetscScalar tt;
206:   KSP_PGMRES *pgmres = (KSP_PGMRES *)ksp->data;

208:   PetscFunctionBegin;
209:   /* Solve for solution vector that minimizes the residual */

211:   if (it < 0) {                        /* no pgmres steps have been performed */
212:     PetscCall(VecCopy(vguess, vdest)); /* VecCopy() is smart, exits immediately if vguess == vdest */
213:     PetscFunctionReturn(PETSC_SUCCESS);
214:   }

216:   /* solve the upper triangular system - RS is the right side and HH is
217:      the upper triangular matrix  - put soln in nrs */
218:   if (*HH(it, it) != 0.0) nrs[it] = *RS(it) / *HH(it, it);
219:   else nrs[it] = 0.0;

221:   for (PetscInt k = it - 1; k >= 0; k--) {
222:     tt = *RS(k);
223:     for (PetscInt j = k + 1; j <= it; j++) tt -= *HH(k, j) * nrs[j];
224:     nrs[k] = tt / *HH(k, k);
225:   }

227:   /* Accumulate the correction to the solution of the preconditioned problem in TEMP */
228:   PetscCall(VecMAXPBY(VEC_TEMP, it + 1, nrs, 0, &VEC_VV(0)));
229:   PetscCall(KSPUnwindPreconditioner(ksp, VEC_TEMP, VEC_TEMP_MATOP));
230:   /* add solution to previous solution */
231:   if (vdest == vguess) PetscCall(VecAXPY(vdest, 1.0, VEC_TEMP));
232:   else PetscCall(VecWAXPY(vdest, 1.0, VEC_TEMP, vguess));
233:   PetscFunctionReturn(PETSC_SUCCESS);
234: }

236: static PetscErrorCode KSPPGMRESUpdateHessenberg(KSP ksp, PetscInt it, PetscBool *hapend, PetscReal *res)
237: {
238:   PetscScalar *hh, *cc, *ss, *rs;
239:   PetscReal    hapbnd;
240:   KSP_PGMRES  *pgmres = (KSP_PGMRES *)ksp->data;

242:   PetscFunctionBegin;
243:   hh = HH(0, it); /* pointer to beginning of column to update */
244:   cc = CC(0);     /* beginning of cosine rotations */
245:   ss = SS(0);     /* beginning of sine rotations */
246:   rs = RS(0);     /* right-hand side of least squares system */

248:   /* The Hessenberg matrix is now correct through column it, save that form for possible spectral analysis */
249:   for (PetscInt j = 0; j <= it + 1; j++) *HES(j, it) = hh[j];

251:   /* check for the happy breakdown */
252:   hapbnd = PetscMin(PetscAbsScalar(hh[it + 1] / rs[it]), pgmres->haptol);
253:   if (PetscAbsScalar(hh[it + 1]) < hapbnd) {
254:     PetscCall(PetscInfo(ksp, "Detected happy breakdown, current hapbnd = %14.12e H(%" PetscInt_FMT ",%" PetscInt_FMT ") = %14.12e\n", (double)hapbnd, it + 1, it, (double)PetscAbsScalar(*HH(it + 1, it))));
255:     *hapend = PETSC_TRUE;
256:   }

258:   /* Apply all the previously computed plane rotations to the new column of the Hessenberg matrix */
259:   /* Note: this uses the rotation [conj(c)  s ; -s   c], c= cos(theta), s= sin(theta),
260:      and some refs have [c   s ; -conj(s)  c] (don't be confused!) */

262:   for (PetscInt j = 0; j < it; j++) {
263:     PetscScalar hhj = hh[j];
264:     hh[j]           = PetscConj(cc[j]) * hhj + ss[j] * hh[j + 1];
265:     hh[j + 1]       = -ss[j] * hhj + cc[j] * hh[j + 1];
266:   }

268:   /*
269:     compute the new plane rotation, and apply it to:
270:      1) the right-hand side of the Hessenberg system (RS)
271:         note: it affects RS(it) and RS(it+1)
272:      2) the new column of the Hessenberg matrix
273:         note: it affects HH(it,it) which is currently pointed to
274:         by hh and HH(it+1, it) (*(hh+1))
275:     thus obtaining the updated value of the residual...
276:   */

278:   /* compute new plane rotation */

280:   if (!*hapend) {
281:     PetscReal delta = PetscSqrtReal(PetscSqr(PetscAbsScalar(hh[it])) + PetscSqr(PetscAbsScalar(hh[it + 1])));
282:     if (delta == 0.0) {
283:       ksp->reason = KSP_DIVERGED_NULL;
284:       PetscFunctionReturn(PETSC_SUCCESS);
285:     }

287:     cc[it] = hh[it] / delta;     /* new cosine value */
288:     ss[it] = hh[it + 1] / delta; /* new sine value */

290:     hh[it]     = PetscConj(cc[it]) * hh[it] + ss[it] * hh[it + 1];
291:     rs[it + 1] = -ss[it] * rs[it];
292:     rs[it]     = PetscConj(cc[it]) * rs[it];
293:     *res       = PetscAbsScalar(rs[it + 1]);
294:   } else { /* happy breakdown: HH(it+1, it) = 0, therefore we don't need to apply
295:             another rotation matrix (so RH doesn't change).  The new residual is
296:             always the new sine term times the residual from last time (RS(it)),
297:             but now the new sine rotation would be zero...so the residual should
298:             be zero...so we will multiply "zero" by the last residual.  This might
299:             not be exactly what we want to do here -could just return "zero". */
300:     *res = 0.0;
301:   }
302:   PetscFunctionReturn(PETSC_SUCCESS);
303: }

305: static PetscErrorCode KSPBuildSolution_PGMRES(KSP ksp, Vec ptr, Vec *result)
306: {
307:   KSP_PGMRES *pgmres = (KSP_PGMRES *)ksp->data;

309:   PetscFunctionBegin;
310:   if (!ptr) {
311:     if (!pgmres->sol_temp) PetscCall(VecDuplicate(ksp->vec_sol, &pgmres->sol_temp));
312:     ptr = pgmres->sol_temp;
313:   }
314:   if (!pgmres->nrs) {
315:     /* allocate the work area */
316:     PetscCall(PetscMalloc1(pgmres->max_k, &pgmres->nrs));
317:   }

319:   PetscCall(KSPPGMRESBuildSoln(pgmres->nrs, ksp->vec_sol, ptr, ksp, pgmres->it));
320:   if (result) *result = ptr;
321:   PetscFunctionReturn(PETSC_SUCCESS);
322: }

324: static PetscErrorCode KSPSetFromOptions_PGMRES(KSP ksp, PetscOptionItems PetscOptionsObject)
325: {
326:   PetscFunctionBegin;
327:   PetscCall(KSPSetFromOptions_GMRES(ksp, PetscOptionsObject));
328:   PetscOptionsHeadBegin(PetscOptionsObject, "KSP pipelined GMRES Options");
329:   PetscOptionsHeadEnd();
330:   PetscFunctionReturn(PETSC_SUCCESS);
331: }

333: static PetscErrorCode KSPReset_PGMRES(KSP ksp)
334: {
335:   PetscFunctionBegin;
336:   PetscCall(KSPReset_GMRES(ksp));
337:   PetscFunctionReturn(PETSC_SUCCESS);
338: }

340: /*MC
341:    KSPPGMRES - Implements the Pipelined Generalized Minimal Residual method {cite}`ghyselsashbymeerbergenvanroose2013`. [](sec_pipelineksp)

343:    Options Database Keys:
344: +   -ksp_gmres_restart restart                                                  - the number of Krylov directions to orthogonalize against
345: .   -ksp_gmres_haptol tol                                                       - sets the tolerance for "happy breakdown" (exact convergence)
346: .   -ksp_gmres_preallocate (true|false)                                         - preallocate all the Krylov search directions initially
347:                                                                                   (otherwise groups of vectors are allocated as needed)
348: -   -ksp_gmres_krylov_monitor                                                   - plot the Krylov space generated

350:    Level: beginner

352:    Note:
353:    MPI configuration may be necessary for reductions to make asynchronous progress, which is important for performance of pipelined methods.
354:    See [](doc_faq_pipelined)

356:    Developer Note:
357:    This object is subclassed off of `KSPGMRES`, see the source code in src/ksp/ksp/impls/gmres for comments on the structure of the code

359: .seealso: [](ch_ksp), [](sec_pipelineksp), [](doc_faq_pipelined), `KSPCreate()`, `KSPSetType()`, `KSPType`, `KSP`, `KSPGMRES`, `KSPLGMRES`, `KSPPIPECG`, `KSPPIPECR`,
360:           `KSPGMRESSetRestart()`, `KSPGMRESSetHapTol()`, `KSPGMRESSetPreAllocateVectors()`, `KSPOrthogonalizationSet()`, `KSPOrthogonalizationGet()`,
361:           `KSPOrthogonalizationClassicalGramSchmidt()`, `KSPOrthogonalizationModifiedGramSchmidt()`,
362:           `KSPOrthogonalizationCGSRefinementType`, `KSPOrthogonalizationSetCGSRefinementType()`, `KSPOrthogonalizationGetCGSRefinementType()`, `KSPGMRESMonitorKrylov()`
363: M*/

365: PETSC_EXTERN PetscErrorCode KSPCreate_PGMRES(KSP ksp)
366: {
367:   KSP_PGMRES *pgmres;

369:   PetscFunctionBegin;
370:   PetscCall(PetscNew(&pgmres));

372:   ksp->data                              = (void *)pgmres;
373:   ksp->ops->buildsolution                = KSPBuildSolution_PGMRES;
374:   ksp->ops->setup                        = KSPSetUp_PGMRES;
375:   ksp->ops->solve                        = KSPSolve_PGMRES;
376:   ksp->ops->reset                        = KSPReset_PGMRES;
377:   ksp->ops->destroy                      = KSPDestroy_PGMRES;
378:   ksp->ops->view                         = KSPView_GMRES;
379:   ksp->ops->setfromoptions               = KSPSetFromOptions_PGMRES;
380:   ksp->ops->computeextremesingularvalues = KSPComputeExtremeSingularValues_GMRES;
381:   ksp->ops->computeeigenvalues           = KSPComputeEigenvalues_GMRES;

383:   PetscCall(KSPSetSupportedNorm(ksp, KSP_NORM_PRECONDITIONED, PC_LEFT, 3));
384:   PetscCall(KSPSetSupportedNorm(ksp, KSP_NORM_UNPRECONDITIONED, PC_RIGHT, 2));
385:   PetscCall(KSPSetSupportedNorm(ksp, KSP_NORM_NONE, PC_RIGHT, 1));

387:   PetscCall(PetscObjectComposeFunction((PetscObject)ksp, "KSPGMRESSetPreAllocateVectors_C", KSPGMRESSetPreAllocateVectors_GMRES));
388:   PetscCall(PetscObjectComposeFunction((PetscObject)ksp, "KSPGMRESSetRestart_C", KSPGMRESSetRestart_GMRES));
389:   PetscCall(PetscObjectComposeFunction((PetscObject)ksp, "KSPGMRESGetRestart_C", KSPGMRESGetRestart_GMRES));

391:   pgmres->nextra_vecs    = 1;
392:   pgmres->haptol         = 1.0e-30;
393:   pgmres->q_preallocate  = PETSC_FALSE;
394:   pgmres->delta_allocate = PGMRES_DELTA_DIRECTIONS;
395:   pgmres->nrs            = NULL;
396:   pgmres->sol_temp       = NULL;
397:   pgmres->max_k          = PGMRES_DEFAULT_MAXK;
398:   pgmres->Rsvd           = NULL;
399:   PetscFunctionReturn(PETSC_SUCCESS);
400: }