xref: /petsc/src/ksp/ksp/impls/gmres/lgmres/lgmres.c (revision f6714f7e2f29a00d39a12212bd9dea2f7b8ed983)
1 #include <../src/ksp/ksp/impls/gmres/lgmres/lgmresimpl.h> /*I "petscksp.h" I*/
2 
3 static PetscErrorCode KSPLGMRESGetNewVectors(KSP, PetscInt);
4 static PetscErrorCode KSPLGMRESUpdateHessenberg(KSP, PetscInt, PetscBool, PetscReal *);
5 static PetscErrorCode KSPLGMRESBuildSoln(PetscScalar *, Vec, Vec, KSP, PetscInt);
6 
7 /*@
8   KSPLGMRESSetAugDim - Set the number of error approximations to include in the approximation space (default is 2) for `KSPLGMRES`
9 
10   Collective
11 
12   Input Parameters:
13 + ksp - the `KSP` context
14 - dim - the number of vectors to use
15 
16   Options Database Key:
17 . -ksp_lgmres_augment dim - the number of error approximations to include
18 
19   Level: intermediate
20 
21   Note:
22   If this is set to zero, then this method is equivalent to `KSPGMRES`
23 
24 .seealso: [](ch_ksp), `KSPLGMRES`, `KSPLGMRESSetConstant()`
25 @*/
KSPLGMRESSetAugDim(KSP ksp,PetscInt dim)26 PetscErrorCode KSPLGMRESSetAugDim(KSP ksp, PetscInt dim)
27 {
28   PetscFunctionBegin;
29   PetscTryMethod(ksp, "KSPLGMRESSetAugDim_C", (KSP, PetscInt), (ksp, dim));
30   PetscFunctionReturn(PETSC_SUCCESS);
31 }
32 
33 /*@
34   KSPLGMRESSetConstant - keep the error approximation space a constant size for every restart cycle
35 
36   Collective
37 
38   Input Parameters:
39 . ksp - the `KSP` context
40 
41   Options Database Key:
42 . -ksp_lgmres_constant - set the size to be constant
43 
44   Level: intermediate
45 
46   Note:
47   This only affects the first couple of restart cycles when the total number of desired error approximations may not
48   be available.
49 
50 .seealso: [](ch_ksp), `KSPLGMRES`, `KSPLGMRESSetAugDim()`
51 @*/
KSPLGMRESSetConstant(KSP ksp)52 PetscErrorCode KSPLGMRESSetConstant(KSP ksp)
53 {
54   PetscFunctionBegin;
55   PetscTryMethod(ksp, "KSPLGMRESSetConstant_C", (KSP), (ksp));
56   PetscFunctionReturn(PETSC_SUCCESS);
57 }
58 
KSPSetUp_LGMRES(KSP ksp)59 static PetscErrorCode KSPSetUp_LGMRES(KSP ksp)
60 {
61   PetscInt    max_k, k, aug_dim;
62   KSP_LGMRES *lgmres = (KSP_LGMRES *)ksp->data;
63 
64   PetscFunctionBegin;
65   max_k   = lgmres->max_k;
66   aug_dim = lgmres->aug_dim;
67   PetscCall(KSPSetUp_GMRES(ksp));
68 
69   /* need array of pointers to augvecs*/
70   PetscCall(PetscMalloc1(2 * aug_dim + AUG_OFFSET, &lgmres->augvecs));
71 
72   lgmres->aug_vecs_allocated = 2 * aug_dim + AUG_OFFSET;
73 
74   PetscCall(PetscMalloc1(2 * aug_dim + AUG_OFFSET, &lgmres->augvecs_user_work));
75   PetscCall(PetscMalloc1(aug_dim, &lgmres->aug_order));
76 
77   /*  for now we will preallocate the augvecs - because aug_dim << restart
78      ... also keep in mind that we need to keep augvecs from cycle to cycle*/
79   lgmres->aug_vv_allocated = 2 * aug_dim + AUG_OFFSET;
80   lgmres->augwork_alloc    = 2 * aug_dim + AUG_OFFSET;
81 
82   PetscCall(KSPCreateVecs(ksp, lgmres->aug_vv_allocated, &lgmres->augvecs_user_work[0], 0, NULL));
83   PetscCall(PetscMalloc1(max_k + 1, &lgmres->hwork));
84   for (k = 0; k < lgmres->aug_vv_allocated; k++) lgmres->augvecs[k] = lgmres->augvecs_user_work[0][k];
85   PetscFunctionReturn(PETSC_SUCCESS);
86 }
87 
KSPLGMRESCycle(PetscInt * itcount,KSP ksp)88 static PetscErrorCode KSPLGMRESCycle(PetscInt *itcount, KSP ksp)
89 {
90   KSP_LGMRES *lgmres = (KSP_LGMRES *)ksp->data;
91   PetscReal   res_norm, res;
92   PetscReal   hapbnd, tt;
93   PetscScalar tmp;
94   PetscBool   hapend = PETSC_FALSE;   /* indicates happy breakdown ending */
95   PetscInt    loc_it;                 /* local count of # of dir. in Krylov space */
96   PetscInt    max_k  = lgmres->max_k; /* max approx space size */
97   PetscInt    max_it = ksp->max_it;   /* max # of overall iterations for the method */
98 
99   /* LGMRES_MOD - new variables*/
100   PetscInt     aug_dim = lgmres->aug_dim;
101   PetscInt     spot    = 0;
102   PetscInt     order   = 0;
103   PetscInt     it_arnoldi; /* number of arnoldi steps to take */
104   PetscInt     it_total;   /* total number of its to take (=approx space size)*/
105   PetscInt     ii, jj;
106   PetscReal    tmp_norm;
107   PetscScalar  inv_tmp_norm;
108   PetscScalar *avec;
109 
110   PetscFunctionBegin;
111   /* Number of pseudo iterations since last restart is the number of prestart directions */
112   loc_it = 0;
113 
114   /* LGMRES_MOD: determine number of arnoldi steps to take */
115   /* if approx_constant then we keep the space the same size even if
116      we don't have the full number of aug vectors yet*/
117   if (lgmres->approx_constant) it_arnoldi = max_k - lgmres->aug_ct;
118   else it_arnoldi = max_k - aug_dim;
119 
120   it_total = it_arnoldi + lgmres->aug_ct;
121 
122   /* initial residual is in VEC_VV(0)  - compute its norm*/
123   PetscCall(VecNorm(VEC_VV(0), NORM_2, &res_norm));
124   KSPCheckNorm(ksp, res_norm);
125   res = res_norm;
126 
127   /* first entry in the right-hand side of the Hessenberg system is just the initial residual norm */
128   *GRS(0) = res_norm;
129 
130   /* check for the convergence */
131   if (!res) {
132     if (itcount) *itcount = 0;
133     ksp->reason = KSP_CONVERGED_ATOL;
134     PetscCall(PetscInfo(ksp, "Converged due to zero residual norm on entry\n"));
135     PetscFunctionReturn(PETSC_SUCCESS);
136   }
137 
138   /* scale VEC_VV (the initial residual) */
139   tmp = 1.0 / res_norm;
140   PetscCall(VecScale(VEC_VV(0), tmp));
141 
142   if (ksp->normtype != KSP_NORM_NONE) ksp->rnorm = res;
143   else ksp->rnorm = 0.0;
144 
145   /* note: (lgmres->it) is always set one less than (loc_it) It is used in
146      KSPBUILDSolution_LGMRES, where it is passed to KSPLGMRESBuildSoln.
147      Note that when KSPLGMRESBuildSoln is called from this function,
148      (loc_it -1) is passed, so the two are equivalent */
149   lgmres->it = (loc_it - 1);
150 
151   /* MAIN ITERATION LOOP BEGINNING*/
152 
153   /* keep iterating until we have converged OR generated the max number
154      of directions OR reached the max number of iterations for the method */
155   PetscCall((*ksp->converged)(ksp, ksp->its, res, &ksp->reason, ksp->cnvP));
156 
157   while (!ksp->reason && loc_it < it_total && ksp->its < max_it) { /* LGMRES_MOD: changed to it_total */
158     PetscCall(KSPLogResidualHistory(ksp, res));
159     lgmres->it = (loc_it - 1);
160     PetscCall(KSPMonitor(ksp, ksp->its, res));
161 
162     /* see if more space is needed for work vectors */
163     if (lgmres->vv_allocated <= loc_it + VEC_OFFSET + 1) {
164       PetscCall(KSPLGMRESGetNewVectors(ksp, loc_it + 1));
165       /* (loc_it+1) is passed in as number of the first vector that should be allocated */
166     }
167 
168     /* LGMRES_MOD: decide whether this is an arnoldi step or an aug step */
169     if (loc_it < it_arnoldi) { /* Arnoldi */
170       PetscCall(KSP_PCApplyBAorAB(ksp, VEC_VV(loc_it), VEC_VV(1 + loc_it), VEC_TEMP_MATOP));
171     } else {                           /* aug step */
172       order = loc_it - it_arnoldi + 1; /* which aug step */
173       for (ii = 0; ii < aug_dim; ii++) {
174         if (lgmres->aug_order[ii] == order) {
175           spot = ii;
176           break; /* must have this because there will be duplicates before aug_ct = aug_dim */
177         }
178       }
179 
180       PetscCall(VecCopy(A_AUGVEC(spot), VEC_VV(1 + loc_it)));
181       /* note: an alternate implementation choice would be to only save the AUGVECS and
182          not A_AUGVEC and then apply the PC here to the augvec */
183     }
184 
185     /* update Hessenberg matrix and do Gram-Schmidt - new direction is in VEC_VV(1+loc_it)*/
186     PetscCall((*lgmres->orthog)(ksp, loc_it));
187 
188     /* new entry in hessenburg is the 2-norm of our new direction */
189     PetscCall(VecNorm(VEC_VV(loc_it + 1), NORM_2, &tt));
190 
191     *HH(loc_it + 1, loc_it)  = tt;
192     *HES(loc_it + 1, loc_it) = tt;
193 
194     /* check for the happy breakdown */
195     hapbnd = PetscAbsScalar(tt / *GRS(loc_it)); /* GRS(loc_it) contains the res_norm from the last iteration  */
196     if (hapbnd > lgmres->haptol) hapbnd = lgmres->haptol;
197     if (tt > hapbnd) {
198       tmp = 1.0 / tt;
199       PetscCall(VecScale(VEC_VV(loc_it + 1), tmp)); /* scale new direction by its norm */
200     } else {
201       PetscCall(PetscInfo(ksp, "Detected happy breakdown, current hapbnd = %g tt = %g\n", (double)hapbnd, (double)tt));
202       hapend = PETSC_TRUE;
203     }
204 
205     /* apply rotations to the new column of the Hessenberg (and the right-hand side of the system),
206        calculate new rotation, and get new residual norm at the same time*/
207     PetscCall(KSPLGMRESUpdateHessenberg(ksp, loc_it, hapend, &res));
208     if (ksp->reason) break;
209 
210     loc_it++;
211     lgmres->it = (loc_it - 1); /* Add this here in case it has converged */
212 
213     PetscCall(PetscObjectSAWsTakeAccess((PetscObject)ksp));
214     ksp->its++;
215     if (ksp->normtype != KSP_NORM_NONE) ksp->rnorm = res;
216     else ksp->rnorm = 0.0;
217     PetscCall(PetscObjectSAWsGrantAccess((PetscObject)ksp));
218 
219     PetscCall((*ksp->converged)(ksp, ksp->its, res, &ksp->reason, ksp->cnvP));
220 
221     /* Catch error in happy breakdown and signal convergence and break from loop */
222     if (hapend) {
223       if (!ksp->reason) {
224         PetscCheck(!ksp->errorifnotconverged, PetscObjectComm((PetscObject)ksp), PETSC_ERR_NOT_CONVERGED, "Reached happy break down, but convergence was not indicated. Residual norm = %g", (double)res);
225         ksp->reason = KSP_DIVERGED_BREAKDOWN;
226         break;
227       }
228     }
229   }
230   /* END OF ITERATION LOOP */
231   PetscCall(KSPLogResidualHistory(ksp, res));
232 
233   if (itcount) *itcount = loc_it;
234 
235   /*
236     Solve for the "best" coefficients of the Krylov
237     columns, add the solution values together, and possibly unwind the
238     preconditioning from the solution
239    */
240 
241   /* Form the solution (or the solution so far) */
242   /* Note: must pass in (loc_it-1) for iteration count so that KSPLGMRESBuildSoln properly navigates */
243 
244   PetscCall(KSPLGMRESBuildSoln(GRS(0), ksp->vec_sol, ksp->vec_sol, ksp, loc_it - 1));
245 
246   /* Monitor if we know that we will not return for a restart */
247   if (ksp->reason == KSP_CONVERGED_ITERATING && ksp->its >= ksp->max_it) ksp->reason = KSP_DIVERGED_ITS;
248   if (ksp->reason) PetscCall(KSPMonitor(ksp, ksp->its, res));
249 
250   /* LGMRES_MOD collect aug vector and A*augvector for future restarts -
251      only if we will be restarting (i.e. this cycle performed it_total iterations)  */
252   if (!ksp->reason && ksp->its < max_it && aug_dim > 0) {
253     /* AUG_TEMP contains the new augmentation vector (assigned in  KSPLGMRESBuildSoln) */
254     if (!lgmres->aug_ct) {
255       spot = 0;
256       lgmres->aug_ct++;
257     } else if (lgmres->aug_ct < aug_dim) {
258       spot = lgmres->aug_ct;
259       lgmres->aug_ct++;
260     } else { /* truncate */
261       for (ii = 0; ii < aug_dim; ii++) {
262         if (lgmres->aug_order[ii] == aug_dim) spot = ii;
263       }
264     }
265 
266     PetscCall(VecCopy(AUG_TEMP, AUGVEC(spot)));
267     /* need to normalize */
268     PetscCall(VecNorm(AUGVEC(spot), NORM_2, &tmp_norm));
269 
270     inv_tmp_norm = 1.0 / tmp_norm;
271 
272     PetscCall(VecScale(AUGVEC(spot), inv_tmp_norm));
273 
274     /* set new aug vector to order 1  - move all others back one */
275     for (ii = 0; ii < aug_dim; ii++) AUG_ORDER(ii)++;
276     AUG_ORDER(spot) = 1;
277 
278     /* now add the A*aug vector to A_AUGVEC(spot) - this is independent of preconditioning type */
279     /* want V*H*y - y is in GRS, V is in VEC_VV and H is in HES */
280 
281     /* do H+*y */
282     avec = lgmres->hwork;
283     PetscCall(PetscArrayzero(avec, it_total + 1));
284     for (ii = 0; ii < it_total + 1; ii++) {
285       for (jj = 0; jj <= ii + 1 && jj < it_total + 1; jj++) avec[jj] += *HES(jj, ii) * *GRS(ii);
286     }
287 
288     /* multiply result by V+ */
289     PetscCall(VecMAXPBY(VEC_TEMP, it_total + 1, avec, 0, &VEC_VV(0))); /* answer is in VEC_TEMP */
290 
291     /* copy answer to aug location  and scale */
292     PetscCall(VecCopy(VEC_TEMP, A_AUGVEC(spot)));
293     PetscCall(VecScale(A_AUGVEC(spot), inv_tmp_norm));
294   }
295   PetscFunctionReturn(PETSC_SUCCESS);
296 }
297 
KSPSolve_LGMRES(KSP ksp)298 static PetscErrorCode KSPSolve_LGMRES(KSP ksp)
299 {
300   PetscInt    itcount; /* running total of iterations, incl. those in restarts */
301   KSP_LGMRES *lgmres     = (KSP_LGMRES *)ksp->data;
302   PetscBool   guess_zero = ksp->guess_zero;
303   PetscInt    ii; /* LGMRES_MOD variable */
304 
305   PetscFunctionBegin;
306   PetscCheck(!ksp->calc_sings || lgmres->Rsvd, PetscObjectComm((PetscObject)ksp), PETSC_ERR_ORDER, "Must call KSPSetComputeSingularValues() before KSPSetUp() is called");
307 
308   PetscCall(PetscObjectSAWsTakeAccess((PetscObject)ksp));
309 
310   ksp->its        = 0;
311   lgmres->aug_ct  = 0;
312   lgmres->matvecs = 0;
313 
314   PetscCall(PetscObjectSAWsGrantAccess((PetscObject)ksp));
315 
316   /* initialize */
317   itcount = 0;
318   /* LGMRES_MOD */
319   for (ii = 0; ii < lgmres->aug_dim; ii++) lgmres->aug_order[ii] = 0;
320 
321   while (!ksp->reason) {
322     PetscInt cycle_its = 0; /* iterations done in a call to KSPLGMRESCycle */
323     /* calc residual - puts in VEC_VV(0) */
324     PetscCall(KSPInitialResidual(ksp, ksp->vec_sol, VEC_TEMP, VEC_TEMP_MATOP, VEC_VV(0), ksp->vec_rhs));
325     PetscCall(KSPLGMRESCycle(&cycle_its, ksp));
326     itcount += cycle_its;
327     if (itcount >= ksp->max_it) {
328       if (!ksp->reason) ksp->reason = KSP_DIVERGED_ITS;
329       break;
330     }
331     ksp->guess_zero = PETSC_FALSE; /* every future call to KSPInitialResidual() will have nonzero guess */
332   }
333   ksp->guess_zero = guess_zero; /* restore if user provided nonzero initial guess */
334   PetscFunctionReturn(PETSC_SUCCESS);
335 }
336 
KSPDestroy_LGMRES(KSP ksp)337 static PetscErrorCode KSPDestroy_LGMRES(KSP ksp)
338 {
339   KSP_LGMRES *lgmres = (KSP_LGMRES *)ksp->data;
340 
341   PetscFunctionBegin;
342   PetscCall(PetscFree(lgmres->augvecs));
343   if (lgmres->augwork_alloc) PetscCall(VecDestroyVecs(lgmres->augwork_alloc, &lgmres->augvecs_user_work[0]));
344   PetscCall(PetscFree(lgmres->augvecs_user_work));
345   PetscCall(PetscFree(lgmres->aug_order));
346   PetscCall(PetscFree(lgmres->hwork));
347   PetscCall(PetscObjectComposeFunction((PetscObject)ksp, "KSPLGMRESSetConstant_C", NULL));
348   PetscCall(PetscObjectComposeFunction((PetscObject)ksp, "KSPLGMRESSetAugDim_C", NULL));
349   PetscCall(KSPDestroy_GMRES(ksp));
350   PetscFunctionReturn(PETSC_SUCCESS);
351 }
352 
KSPLGMRESBuildSoln(PetscScalar * nrs,Vec vguess,Vec vdest,KSP ksp,PetscInt it)353 static PetscErrorCode KSPLGMRESBuildSoln(PetscScalar *nrs, Vec vguess, Vec vdest, KSP ksp, PetscInt it)
354 {
355   PetscScalar tt;
356   PetscInt    ii, k, j;
357   KSP_LGMRES *lgmres = (KSP_LGMRES *)ksp->data;
358   /* LGMRES_MOD */
359   PetscInt it_arnoldi, it_aug;
360   PetscInt jj, spot = 0;
361 
362   PetscFunctionBegin;
363   /* Solve for solution vector that minimizes the residual */
364 
365   /* If it is < 0, no lgmres steps have been performed */
366   if (it < 0) {
367     PetscCall(VecCopy(vguess, vdest)); /* VecCopy() is smart, exists immediately if vguess == vdest */
368     PetscFunctionReturn(PETSC_SUCCESS);
369   }
370 
371   /* so (it+1) lgmres steps HAVE been performed */
372 
373   /* LGMRES_MOD - determine if we need to use augvecs for the soln  - do not assume that
374      this is called after the total its allowed for an approx space */
375   if (lgmres->approx_constant) {
376     it_arnoldi = lgmres->max_k - lgmres->aug_ct;
377   } else {
378     it_arnoldi = lgmres->max_k - lgmres->aug_dim;
379   }
380   if (it_arnoldi >= it + 1) {
381     it_aug     = 0;
382     it_arnoldi = it + 1;
383   } else {
384     it_aug = (it + 1) - it_arnoldi;
385   }
386 
387   /* now it_arnoldi indicates the number of matvecs that took place */
388   lgmres->matvecs += it_arnoldi;
389 
390   /* solve the upper triangular system - GRS is the right side and HH is
391      the upper triangular matrix  - put soln in nrs */
392   PetscCheck(*HH(it, it) != 0.0, PETSC_COMM_SELF, PETSC_ERR_CONV_FAILED, "HH(it,it) is identically zero; it = %" PetscInt_FMT " GRS(it) = %g", it, (double)PetscAbsScalar(*GRS(it)));
393   if (*HH(it, it) != 0.0) {
394     nrs[it] = *GRS(it) / *HH(it, it);
395   } else {
396     nrs[it] = 0.0;
397   }
398 
399   for (ii = 1; ii <= it; ii++) {
400     k  = it - ii;
401     tt = *GRS(k);
402     for (j = k + 1; j <= it; j++) tt = tt - *HH(k, j) * nrs[j];
403     nrs[k] = tt / *HH(k, k);
404   }
405 
406   /* Accumulate the correction to the soln of the preconditioned prob. in VEC_TEMP */
407 
408   /* LGMRES_MOD - if augmenting has happened we need to form the solution using the augvecs */
409   if (!it_aug) { /* all its are from arnoldi */
410     PetscCall(VecMAXPBY(VEC_TEMP, it + 1, nrs, 0, &VEC_VV(0)));
411   } else { /* use aug vecs */
412     /* first do regular Krylov directions */
413     PetscCall(VecMAXPBY(VEC_TEMP, it_arnoldi, nrs, 0, &VEC_VV(0)));
414     /* now add augmented portions - add contribution of aug vectors one at a time*/
415 
416     for (ii = 0; ii < it_aug; ii++) {
417       for (jj = 0; jj < lgmres->aug_dim; jj++) {
418         if (lgmres->aug_order[jj] == (ii + 1)) {
419           spot = jj;
420           break; /* must have this because there will be duplicates before aug_ct = aug_dim */
421         }
422       }
423       PetscCall(VecAXPY(VEC_TEMP, nrs[it_arnoldi + ii], AUGVEC(spot)));
424     }
425   }
426   /* now VEC_TEMP is what we want to keep for augmenting purposes - grab before the
427      preconditioner is "unwound" from right-precondtioning*/
428   PetscCall(VecCopy(VEC_TEMP, AUG_TEMP));
429 
430   PetscCall(KSPUnwindPreconditioner(ksp, VEC_TEMP, VEC_TEMP_MATOP));
431 
432   /* add solution to previous solution */
433   /* put updated solution into vdest.*/
434   PetscCall(VecCopy(vguess, vdest));
435   PetscCall(VecAXPY(vdest, 1.0, VEC_TEMP));
436   PetscFunctionReturn(PETSC_SUCCESS);
437 }
438 
KSPLGMRESUpdateHessenberg(KSP ksp,PetscInt it,PetscBool hapend,PetscReal * res)439 static PetscErrorCode KSPLGMRESUpdateHessenberg(KSP ksp, PetscInt it, PetscBool hapend, PetscReal *res)
440 {
441   PetscScalar *hh, *cc, *ss, tt;
442   PetscInt     j;
443   KSP_LGMRES  *lgmres = (KSP_LGMRES *)ksp->data;
444 
445   PetscFunctionBegin;
446   hh = HH(0, it); /* pointer to beginning of column to update - so incrementing hh "steps down" the (it+1)th col of HH*/
447   cc = CC(0);     /* beginning of cosine rotations */
448   ss = SS(0);     /* beginning of sine rotations */
449 
450   /* Apply all the previously computed plane rotations to the new column
451      of the Hessenberg matrix */
452   /* Note: this uses the rotation [conj(c)  s ; -s   c], c= cos(theta), s= sin(theta) */
453 
454   for (j = 1; j <= it; j++) {
455     tt  = *hh;
456     *hh = PetscConj(*cc) * tt + *ss * *(hh + 1);
457     hh++;
458     *hh = *cc++ * *hh - (*ss++ * tt);
459     /* hh, cc, and ss have all been incremented one by end of loop */
460   }
461 
462   /*
463     compute the new plane rotation, and apply it to:
464      1) the right-hand side of the Hessenberg system (GRS)
465         note: it affects GRS(it) and GRS(it+1)
466      2) the new column of the Hessenberg matrix
467         note: it affects HH(it,it) which is currently pointed to
468         by hh and HH(it+1, it) (*(hh+1))
469     thus obtaining the updated value of the residual...
470   */
471 
472   /* compute new plane rotation */
473 
474   if (!hapend) {
475     tt = PetscSqrtScalar(PetscConj(*hh) * *hh + PetscConj(*(hh + 1)) * *(hh + 1));
476     if (tt == 0.0) {
477       ksp->reason = KSP_DIVERGED_NULL;
478       PetscFunctionReturn(PETSC_SUCCESS);
479     }
480     *cc = *hh / tt;       /* new cosine value */
481     *ss = *(hh + 1) / tt; /* new sine value */
482 
483     /* apply to 1) and 2) */
484     *GRS(it + 1) = -(*ss * *GRS(it));
485     *GRS(it)     = PetscConj(*cc) * *GRS(it);
486     *hh          = PetscConj(*cc) * *hh + *ss * *(hh + 1);
487 
488     /* residual is the last element (it+1) of right-hand side! */
489     *res = PetscAbsScalar(*GRS(it + 1));
490 
491   } else { /* happy breakdown: HH(it+1, it) = 0, therefore we don't need to apply
492             another rotation matrix (so RH doesn't change).  The new residual is
493             always the new sine term times the residual from last time (GRS(it)),
494             but now the new sine rotation would be zero...so the residual should
495             be zero...so we will multiply "zero" by the last residual.  This might
496             not be exactly what we want to do here -could just return "zero". */
497 
498     *res = 0.0;
499   }
500   PetscFunctionReturn(PETSC_SUCCESS);
501 }
502 
503 /*
504    KSPLGMRESGetNewVectors - Allocates more work vectors, starting from VEC_VV(it)
505 
506 */
KSPLGMRESGetNewVectors(KSP ksp,PetscInt it)507 static PetscErrorCode KSPLGMRESGetNewVectors(KSP ksp, PetscInt it)
508 {
509   KSP_LGMRES *lgmres = (KSP_LGMRES *)ksp->data;
510   PetscInt    nwork  = lgmres->nwork_alloc; /* number of work vector chunks allocated */
511   PetscInt    nalloc;                       /* number to allocate */
512   PetscInt    k;
513 
514   PetscFunctionBegin;
515   nalloc = lgmres->delta_allocate; /* number of vectors to allocate in a single chunk */
516 
517   /* Adjust the number to allocate to make sure that we don't exceed the
518      number of available slots (lgmres->vecs_allocated)*/
519   if (it + VEC_OFFSET + nalloc >= lgmres->vecs_allocated) nalloc = lgmres->vecs_allocated - it - VEC_OFFSET;
520   if (!nalloc) PetscFunctionReturn(PETSC_SUCCESS);
521 
522   lgmres->vv_allocated += nalloc; /* vv_allocated is the number of vectors allocated */
523 
524   /* work vectors */
525   PetscCall(KSPCreateVecs(ksp, nalloc, &lgmres->user_work[nwork], 0, NULL));
526   /* specify size of chunk allocated */
527   lgmres->mwork_alloc[nwork] = nalloc;
528 
529   for (k = 0; k < nalloc; k++) lgmres->vecs[it + VEC_OFFSET + k] = lgmres->user_work[nwork][k];
530 
531   /* LGMRES_MOD - for now we are preallocating the augmentation vectors */
532 
533   /* increment the number of work vector chunks */
534   lgmres->nwork_alloc++;
535   PetscFunctionReturn(PETSC_SUCCESS);
536 }
537 
KSPBuildSolution_LGMRES(KSP ksp,Vec ptr,Vec * result)538 static PetscErrorCode KSPBuildSolution_LGMRES(KSP ksp, Vec ptr, Vec *result)
539 {
540   KSP_LGMRES *lgmres = (KSP_LGMRES *)ksp->data;
541 
542   PetscFunctionBegin;
543   if (!ptr) {
544     if (!lgmres->sol_temp) PetscCall(VecDuplicate(ksp->vec_sol, &lgmres->sol_temp));
545     ptr = lgmres->sol_temp;
546   }
547   if (!lgmres->nrs) {
548     /* allocate the work area */
549     PetscCall(PetscMalloc1(lgmres->max_k, &lgmres->nrs));
550   }
551 
552   PetscCall(KSPLGMRESBuildSoln(lgmres->nrs, ksp->vec_sol, ptr, ksp, lgmres->it));
553   if (result) *result = ptr;
554   PetscFunctionReturn(PETSC_SUCCESS);
555 }
556 
KSPView_LGMRES(KSP ksp,PetscViewer viewer)557 static PetscErrorCode KSPView_LGMRES(KSP ksp, PetscViewer viewer)
558 {
559   KSP_LGMRES *lgmres = (KSP_LGMRES *)ksp->data;
560   PetscBool   isascii;
561 
562   PetscFunctionBegin;
563   PetscCall(KSPView_GMRES(ksp, viewer));
564   PetscCall(PetscObjectTypeCompare((PetscObject)viewer, PETSCVIEWERASCII, &isascii));
565   if (isascii) {
566     /* LGMRES_MOD */
567     PetscCall(PetscViewerASCIIPrintf(viewer, "  aug. dimension=%" PetscInt_FMT "\n", lgmres->aug_dim));
568     if (lgmres->approx_constant) PetscCall(PetscViewerASCIIPrintf(viewer, "  approx. space size was kept constant.\n"));
569     PetscCall(PetscViewerASCIIPrintf(viewer, "  number of matvecs=%" PetscInt_FMT "\n", lgmres->matvecs));
570   }
571   PetscFunctionReturn(PETSC_SUCCESS);
572 }
573 
KSPSetFromOptions_LGMRES(KSP ksp,PetscOptionItems PetscOptionsObject)574 static PetscErrorCode KSPSetFromOptions_LGMRES(KSP ksp, PetscOptionItems PetscOptionsObject)
575 {
576   PetscInt    aug;
577   KSP_LGMRES *lgmres = (KSP_LGMRES *)ksp->data;
578   PetscBool   flg    = PETSC_FALSE;
579 
580   PetscFunctionBegin;
581   PetscCall(KSPSetFromOptions_GMRES(ksp, PetscOptionsObject));
582   PetscOptionsHeadBegin(PetscOptionsObject, "KSP LGMRES Options");
583   PetscCall(PetscOptionsBool("-ksp_lgmres_constant", "Use constant approx. space size", "KSPGMRESSetConstant", lgmres->approx_constant, &lgmres->approx_constant, NULL));
584   PetscCall(PetscOptionsInt("-ksp_lgmres_augment", "Number of error approximations to augment the Krylov space with", "KSPLGMRESSetAugDim", lgmres->aug_dim, &aug, &flg));
585   if (flg) PetscCall(KSPLGMRESSetAugDim(ksp, aug));
586   PetscOptionsHeadEnd();
587   PetscFunctionReturn(PETSC_SUCCESS);
588 }
589 
KSPLGMRESSetConstant_LGMRES(KSP ksp)590 static PetscErrorCode KSPLGMRESSetConstant_LGMRES(KSP ksp)
591 {
592   KSP_LGMRES *lgmres = (KSP_LGMRES *)ksp->data;
593 
594   PetscFunctionBegin;
595   lgmres->approx_constant = PETSC_TRUE;
596   PetscFunctionReturn(PETSC_SUCCESS);
597 }
598 
KSPLGMRESSetAugDim_LGMRES(KSP ksp,PetscInt aug_dim)599 static PetscErrorCode KSPLGMRESSetAugDim_LGMRES(KSP ksp, PetscInt aug_dim)
600 {
601   KSP_LGMRES *lgmres = (KSP_LGMRES *)ksp->data;
602 
603   PetscFunctionBegin;
604   PetscCheck(aug_dim >= 0, PetscObjectComm((PetscObject)ksp), PETSC_ERR_ARG_OUTOFRANGE, "Augmentation dimension must be nonegative");
605   PetscCheck(aug_dim <= (lgmres->max_k - 1), PetscObjectComm((PetscObject)ksp), PETSC_ERR_ARG_OUTOFRANGE, "Augmentation dimension must be <= (restart size-1)");
606   lgmres->aug_dim = aug_dim;
607   PetscFunctionReturn(PETSC_SUCCESS);
608 }
609 
610 /*MC
611   KSPLGMRES - Augments the standard GMRES approximation space with approximations to the error from previous restart cycles as in {cite}`bjm2005`.
612 
613   Options Database Keys:
614 +   -ksp_gmres_restart <restart>                                                - total approximation space size (Krylov directions + error approximations)
615 .   -ksp_gmres_haptol <tol>                                                     - sets the tolerance for "happy ending" (exact convergence)
616 .   -ksp_gmres_preallocate                                                      - preallocate all the Krylov search directions initially
617                                                                                 (otherwise groups of vectors are allocated as needed)
618 .   -ksp_gmres_classicalgramschmidt                                             - use classical (unmodified) Gram-Schmidt to orthogonalize against
619                                                                                 the Krylov space (fast) (the default)
620 .   -ksp_gmres_modifiedgramschmidt                                              - use modified Gram-Schmidt in the orthogonalization (more stable, but slower)
621 .   -ksp_gmres_cgs_refinement_type <refine_never,refine_ifneeded,refine_always> - determine if iterative refinement is used to increase the
622                                                                                 stability of the classical Gram-Schmidt  orthogonalization.
623 .   -ksp_gmres_krylov_monitor                                                   - plot the Krylov space generated
624 .   -ksp_lgmres_augment <k>                                                     - number of error approximations to augment the Krylov space with
625 -   -ksp_lgmres_constant                                                        - use a constant approximate space size
626                                                                                 (only affects restart cycles < num. error approx.(k), i.e. the first k restarts)
627 
628   Level: beginner
629 
630   Notes:
631   Supports both left and right preconditioning, but not symmetric.
632 
633   Augmenting with 1,2, or 3 approximations is generally optimal.
634 
635   This method is an accelerator for `KSPGMRES` - it is not useful for problems that stall. When gmres(m) stalls then lgmres with a size m
636   approximation space will also generally stall.
637 
638   If gmres(m) converges in a small number of restart cycles, then lgmres also tends not to be very helpful.
639 
640   Developer Notes:
641   To run LGMRES(m, k) as described in {cite}`bjm2005`, use\:
642 .vb
643   -ksp_gmres_restart <m+k>
644   -ksp_lgmres_augment <k>
645 .ve
646 
647   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
648 
649   Contributed by:
650   Allison Baker
651 
652 .seealso: [](ch_ksp), `KSPCreate()`, `KSPSetType()`, `KSPType`, `KSP`, `KSPFGMRES`, `KSPGMRES`,
653           `KSPGMRESSetRestart()`, `KSPGMRESSetHapTol()`, `KSPGMRESSetPreAllocateVectors()`, `KSPGMRESSetOrthogonalization()`, `KSPGMRESGetOrthogonalization()`,
654           `KSPGMRESClassicalGramSchmidtOrthogonalization()`, `KSPGMRESModifiedGramSchmidtOrthogonalization()`,
655           `KSPGMRESCGSRefinementType`, `KSPGMRESSetCGSRefinementType()`, `KSPGMRESGetCGSRefinementType()`, `KSPGMRESMonitorKrylov()`, `KSPLGMRESSetAugDim()`,
656           `KSPGMRESSetConstant()`, `KSPLGMRESSetConstant()`, `KSPLGMRESSetAugDim()`
657 M*/
658 
KSPCreate_LGMRES(KSP ksp)659 PETSC_EXTERN PetscErrorCode KSPCreate_LGMRES(KSP ksp)
660 {
661   KSP_LGMRES *lgmres;
662 
663   PetscFunctionBegin;
664   PetscCall(PetscNew(&lgmres));
665 
666   ksp->data               = (void *)lgmres;
667   ksp->ops->buildsolution = KSPBuildSolution_LGMRES;
668 
669   ksp->ops->setup                        = KSPSetUp_LGMRES;
670   ksp->ops->solve                        = KSPSolve_LGMRES;
671   ksp->ops->destroy                      = KSPDestroy_LGMRES;
672   ksp->ops->view                         = KSPView_LGMRES;
673   ksp->ops->setfromoptions               = KSPSetFromOptions_LGMRES;
674   ksp->ops->computeextremesingularvalues = KSPComputeExtremeSingularValues_GMRES;
675   ksp->ops->computeeigenvalues           = KSPComputeEigenvalues_GMRES;
676 
677   PetscCall(KSPSetSupportedNorm(ksp, KSP_NORM_PRECONDITIONED, PC_LEFT, 3));
678   PetscCall(KSPSetSupportedNorm(ksp, KSP_NORM_UNPRECONDITIONED, PC_RIGHT, 2));
679   PetscCall(KSPSetSupportedNorm(ksp, KSP_NORM_NONE, PC_RIGHT, 1));
680 
681   PetscCall(PetscObjectComposeFunction((PetscObject)ksp, "KSPGMRESSetPreAllocateVectors_C", KSPGMRESSetPreAllocateVectors_GMRES));
682   PetscCall(PetscObjectComposeFunction((PetscObject)ksp, "KSPGMRESSetOrthogonalization_C", KSPGMRESSetOrthogonalization_GMRES));
683   PetscCall(PetscObjectComposeFunction((PetscObject)ksp, "KSPGMRESGetOrthogonalization_C", KSPGMRESGetOrthogonalization_GMRES));
684   PetscCall(PetscObjectComposeFunction((PetscObject)ksp, "KSPGMRESSetRestart_C", KSPGMRESSetRestart_GMRES));
685   PetscCall(PetscObjectComposeFunction((PetscObject)ksp, "KSPGMRESGetRestart_C", KSPGMRESGetRestart_GMRES));
686   PetscCall(PetscObjectComposeFunction((PetscObject)ksp, "KSPGMRESSetHapTol_C", KSPGMRESSetHapTol_GMRES));
687   PetscCall(PetscObjectComposeFunction((PetscObject)ksp, "KSPGMRESSetCGSRefinementType_C", KSPGMRESSetCGSRefinementType_GMRES));
688   PetscCall(PetscObjectComposeFunction((PetscObject)ksp, "KSPGMRESGetCGSRefinementType_C", KSPGMRESGetCGSRefinementType_GMRES));
689 
690   /* LGMRES_MOD add extra functions here - like the one to set num of aug vectors */
691   PetscCall(PetscObjectComposeFunction((PetscObject)ksp, "KSPLGMRESSetConstant_C", KSPLGMRESSetConstant_LGMRES));
692   PetscCall(PetscObjectComposeFunction((PetscObject)ksp, "KSPLGMRESSetAugDim_C", KSPLGMRESSetAugDim_LGMRES));
693 
694   /* defaults */
695   lgmres->haptol         = 1.0e-30;
696   lgmres->q_preallocate  = PETSC_FALSE;
697   lgmres->delta_allocate = LGMRES_DELTA_DIRECTIONS;
698   lgmres->orthog         = KSPGMRESClassicalGramSchmidtOrthogonalization;
699   lgmres->nrs            = NULL;
700   lgmres->sol_temp       = NULL;
701   lgmres->max_k          = LGMRES_DEFAULT_MAXK;
702   lgmres->Rsvd           = NULL;
703   lgmres->cgstype        = KSP_GMRES_CGS_REFINE_NEVER;
704   lgmres->orthogwork     = NULL;
705 
706   /* LGMRES_MOD - new defaults */
707   lgmres->aug_dim         = LGMRES_DEFAULT_AUGDIM;
708   lgmres->aug_ct          = 0; /* start with no aug vectors */
709   lgmres->approx_constant = PETSC_FALSE;
710   lgmres->matvecs         = 0;
711   PetscFunctionReturn(PETSC_SUCCESS);
712 }
713