xref: /petsc/src/ksp/pc/impls/svd/svd.c (revision e0f5bfbec699682fa3e8b8532b1176849ea4e12a)
1 
2 #include <petsc/private/pcimpl.h> /*I "petscpc.h" I*/
3 #include <petscblaslapack.h>
4 
5 /*
6    Private context (data structure) for the SVD preconditioner.
7 */
8 typedef struct {
9   Vec               diag, work;
10   Mat               A, U, Vt;
11   PetscInt          nzero;
12   PetscReal         zerosing; /* measure of smallest singular value treated as nonzero */
13   PetscInt          essrank;  /* essential rank of operator */
14   VecScatter        left2red, right2red;
15   Vec               leftred, rightred;
16   PetscViewer       monitor;
17   PetscViewerFormat monitorformat;
18 } PC_SVD;
19 
20 typedef enum {
21   READ       = 1,
22   WRITE      = 2,
23   READ_WRITE = 3
24 } AccessMode;
25 
26 /*
27    PCSetUp_SVD - Prepares for the use of the SVD preconditioner
28                     by setting data structures and options.
29 
30    Input Parameter:
31 .  pc - the preconditioner context
32 
33    Application Interface Routine: PCSetUp()
34 
35    Note:
36    The interface routine PCSetUp() is not usually called directly by
37    the user, but instead is called by PCApply() if necessary.
38 */
39 static PetscErrorCode PCSetUp_SVD(PC pc) {
40   PC_SVD      *jac = (PC_SVD *)pc->data;
41   PetscScalar *a, *u, *v, *d, *work;
42   PetscBLASInt nb, lwork;
43   PetscInt     i, n;
44   PetscMPIInt  size;
45 
46   PetscFunctionBegin;
47   PetscCall(MatDestroy(&jac->A));
48   PetscCallMPI(MPI_Comm_size(((PetscObject)pc->pmat)->comm, &size));
49   if (size > 1) {
50     Mat redmat;
51 
52     PetscCall(MatCreateRedundantMatrix(pc->pmat, size, PETSC_COMM_SELF, MAT_INITIAL_MATRIX, &redmat));
53     PetscCall(MatConvert(redmat, MATSEQDENSE, MAT_INITIAL_MATRIX, &jac->A));
54     PetscCall(MatDestroy(&redmat));
55   } else {
56     PetscCall(MatConvert(pc->pmat, MATSEQDENSE, MAT_INITIAL_MATRIX, &jac->A));
57   }
58   if (!jac->diag) { /* assume square matrices */
59     PetscCall(MatCreateVecs(jac->A, &jac->diag, &jac->work));
60   }
61   if (!jac->U) {
62     PetscCall(MatDuplicate(jac->A, MAT_DO_NOT_COPY_VALUES, &jac->U));
63     PetscCall(MatDuplicate(jac->A, MAT_DO_NOT_COPY_VALUES, &jac->Vt));
64   }
65   PetscCall(MatGetSize(jac->A, &n, NULL));
66   if (!n) {
67     PetscCall(PetscInfo(pc, "Matrix has zero rows, skipping svd\n"));
68     PetscFunctionReturn(0);
69   }
70   PetscCall(PetscBLASIntCast(n, &nb));
71   lwork = 5 * nb;
72   PetscCall(PetscMalloc1(lwork, &work));
73   PetscCall(MatDenseGetArray(jac->A, &a));
74   PetscCall(MatDenseGetArray(jac->U, &u));
75   PetscCall(MatDenseGetArray(jac->Vt, &v));
76   PetscCall(VecGetArray(jac->diag, &d));
77 #if !defined(PETSC_USE_COMPLEX)
78   {
79     PetscBLASInt lierr;
80     PetscCall(PetscFPTrapPush(PETSC_FP_TRAP_OFF));
81     PetscCallBLAS("LAPACKgesvd", LAPACKgesvd_("A", "A", &nb, &nb, a, &nb, d, u, &nb, v, &nb, work, &lwork, &lierr));
82     PetscCheck(!lierr, PETSC_COMM_SELF, PETSC_ERR_LIB, "gesv() error %" PetscBLASInt_FMT, lierr);
83     PetscCall(PetscFPTrapPop());
84   }
85 #else
86   {
87     PetscBLASInt lierr;
88     PetscReal   *rwork, *dd;
89     PetscCall(PetscMalloc1(5 * nb, &rwork));
90     PetscCall(PetscMalloc1(nb, &dd));
91     PetscCall(PetscFPTrapPush(PETSC_FP_TRAP_OFF));
92     PetscCallBLAS("LAPACKgesvd", LAPACKgesvd_("A", "A", &nb, &nb, a, &nb, dd, u, &nb, v, &nb, work, &lwork, rwork, &lierr));
93     PetscCheck(!lierr, PETSC_COMM_SELF, PETSC_ERR_LIB, "gesv() error %" PetscBLASInt_FMT, lierr);
94     PetscCall(PetscFree(rwork));
95     for (i = 0; i < n; i++) d[i] = dd[i];
96     PetscCall(PetscFree(dd));
97     PetscCall(PetscFPTrapPop());
98   }
99 #endif
100   PetscCall(MatDenseRestoreArray(jac->A, &a));
101   PetscCall(MatDenseRestoreArray(jac->U, &u));
102   PetscCall(MatDenseRestoreArray(jac->Vt, &v));
103   for (i = n - 1; i >= 0; i--)
104     if (PetscRealPart(d[i]) > jac->zerosing) break;
105   jac->nzero = n - 1 - i;
106   if (jac->monitor) {
107     PetscCall(PetscViewerASCIIAddTab(jac->monitor, ((PetscObject)pc)->tablevel));
108     PetscCall(PetscViewerASCIIPrintf(jac->monitor, "    SVD: condition number %14.12e, %" PetscInt_FMT " of %" PetscInt_FMT " singular values are (nearly) zero\n", (double)PetscRealPart(d[0] / d[n - 1]), jac->nzero, n));
109     if (n < 10 || jac->monitorformat == PETSC_VIEWER_ALL) {
110       PetscCall(PetscViewerASCIIPrintf(jac->monitor, "    SVD: singular values:\n"));
111       for (i = 0; i < n; i++) {
112         if (i % 5 == 0) {
113           if (i != 0) PetscCall(PetscViewerASCIIPrintf(jac->monitor, "\n"));
114           PetscCall(PetscViewerASCIIPrintf(jac->monitor, "        "));
115         }
116         PetscCall(PetscViewerASCIIPrintf(jac->monitor, " %14.12e", (double)PetscRealPart(d[i])));
117       }
118       PetscCall(PetscViewerASCIIPrintf(jac->monitor, "\n"));
119     } else { /* print 5 smallest and 5 largest */
120       PetscCall(PetscViewerASCIIPrintf(jac->monitor, "    SVD: smallest singular values: %14.12e %14.12e %14.12e %14.12e %14.12e\n", (double)PetscRealPart(d[n - 1]), (double)PetscRealPart(d[n - 2]), (double)PetscRealPart(d[n - 3]), (double)PetscRealPart(d[n - 4]), (double)PetscRealPart(d[n - 5])));
121       PetscCall(PetscViewerASCIIPrintf(jac->monitor, "    SVD: largest singular values : %14.12e %14.12e %14.12e %14.12e %14.12e\n", (double)PetscRealPart(d[4]), (double)PetscRealPart(d[3]), (double)PetscRealPart(d[2]), (double)PetscRealPart(d[1]), (double)PetscRealPart(d[0])));
122     }
123     PetscCall(PetscViewerASCIISubtractTab(jac->monitor, ((PetscObject)pc)->tablevel));
124   }
125   PetscCall(PetscInfo(pc, "Largest and smallest singular values %14.12e %14.12e\n", (double)PetscRealPart(d[0]), (double)PetscRealPart(d[n - 1])));
126   for (i = 0; i < n - jac->nzero; i++) d[i] = 1.0 / d[i];
127   for (; i < n; i++) d[i] = 0.0;
128   if (jac->essrank > 0)
129     for (i = 0; i < n - jac->nzero - jac->essrank; i++) d[i] = 0.0; /* Skip all but essrank eigenvalues */
130   PetscCall(PetscInfo(pc, "Number of zero or nearly singular values %" PetscInt_FMT "\n", jac->nzero));
131   PetscCall(VecRestoreArray(jac->diag, &d));
132   PetscCall(PetscFree(work));
133   PetscFunctionReturn(0);
134 }
135 
136 static PetscErrorCode PCSVDGetVec(PC pc, PCSide side, AccessMode amode, Vec x, Vec *xred) {
137   PC_SVD     *jac = (PC_SVD *)pc->data;
138   PetscMPIInt size;
139 
140   PetscFunctionBegin;
141   PetscCallMPI(MPI_Comm_size(PetscObjectComm((PetscObject)pc), &size));
142   *xred = NULL;
143   switch (side) {
144   case PC_LEFT:
145     if (size == 1) *xred = x;
146     else {
147       if (!jac->left2red) PetscCall(VecScatterCreateToAll(x, &jac->left2red, &jac->leftred));
148       if (amode & READ) {
149         PetscCall(VecScatterBegin(jac->left2red, x, jac->leftred, INSERT_VALUES, SCATTER_FORWARD));
150         PetscCall(VecScatterEnd(jac->left2red, x, jac->leftred, INSERT_VALUES, SCATTER_FORWARD));
151       }
152       *xred = jac->leftred;
153     }
154     break;
155   case PC_RIGHT:
156     if (size == 1) *xred = x;
157     else {
158       if (!jac->right2red) PetscCall(VecScatterCreateToAll(x, &jac->right2red, &jac->rightred));
159       if (amode & READ) {
160         PetscCall(VecScatterBegin(jac->right2red, x, jac->rightred, INSERT_VALUES, SCATTER_FORWARD));
161         PetscCall(VecScatterEnd(jac->right2red, x, jac->rightred, INSERT_VALUES, SCATTER_FORWARD));
162       }
163       *xred = jac->rightred;
164     }
165     break;
166   default: SETERRQ(PetscObjectComm((PetscObject)pc), PETSC_ERR_PLIB, "Side must be LEFT or RIGHT");
167   }
168   PetscFunctionReturn(0);
169 }
170 
171 static PetscErrorCode PCSVDRestoreVec(PC pc, PCSide side, AccessMode amode, Vec x, Vec *xred) {
172   PC_SVD     *jac = (PC_SVD *)pc->data;
173   PetscMPIInt size;
174 
175   PetscFunctionBegin;
176   PetscCallMPI(MPI_Comm_size(PetscObjectComm((PetscObject)pc), &size));
177   switch (side) {
178   case PC_LEFT:
179     if (size != 1 && amode & WRITE) {
180       PetscCall(VecScatterBegin(jac->left2red, jac->leftred, x, INSERT_VALUES, SCATTER_REVERSE));
181       PetscCall(VecScatterEnd(jac->left2red, jac->leftred, x, INSERT_VALUES, SCATTER_REVERSE));
182     }
183     break;
184   case PC_RIGHT:
185     if (size != 1 && amode & WRITE) {
186       PetscCall(VecScatterBegin(jac->right2red, jac->rightred, x, INSERT_VALUES, SCATTER_REVERSE));
187       PetscCall(VecScatterEnd(jac->right2red, jac->rightred, x, INSERT_VALUES, SCATTER_REVERSE));
188     }
189     break;
190   default: SETERRQ(PetscObjectComm((PetscObject)pc), PETSC_ERR_PLIB, "Side must be LEFT or RIGHT");
191   }
192   *xred = NULL;
193   PetscFunctionReturn(0);
194 }
195 
196 /*
197    PCApply_SVD - Applies the SVD preconditioner to a vector.
198 
199    Input Parameters:
200 .  pc - the preconditioner context
201 .  x - input vector
202 
203    Output Parameter:
204 .  y - output vector
205 
206    Application Interface Routine: PCApply()
207  */
208 static PetscErrorCode PCApply_SVD(PC pc, Vec x, Vec y) {
209   PC_SVD *jac  = (PC_SVD *)pc->data;
210   Vec     work = jac->work, xred, yred;
211 
212   PetscFunctionBegin;
213   PetscCall(PCSVDGetVec(pc, PC_RIGHT, READ, x, &xred));
214   PetscCall(PCSVDGetVec(pc, PC_LEFT, WRITE, y, &yred));
215 #if !defined(PETSC_USE_COMPLEX)
216   PetscCall(MatMultTranspose(jac->U, xred, work));
217 #else
218   PetscCall(MatMultHermitianTranspose(jac->U, xred, work));
219 #endif
220   PetscCall(VecPointwiseMult(work, work, jac->diag));
221 #if !defined(PETSC_USE_COMPLEX)
222   PetscCall(MatMultTranspose(jac->Vt, work, yred));
223 #else
224   PetscCall(MatMultHermitianTranspose(jac->Vt, work, yred));
225 #endif
226   PetscCall(PCSVDRestoreVec(pc, PC_RIGHT, READ, x, &xred));
227   PetscCall(PCSVDRestoreVec(pc, PC_LEFT, WRITE, y, &yred));
228   PetscFunctionReturn(0);
229 }
230 
231 static PetscErrorCode PCMatApply_SVD(PC pc, Mat X, Mat Y) {
232   PC_SVD *jac = (PC_SVD *)pc->data;
233   Mat     W;
234 
235   PetscFunctionBegin;
236   PetscCall(MatTransposeMatMult(jac->U, X, MAT_INITIAL_MATRIX, PETSC_DEFAULT, &W));
237   PetscCall(MatDiagonalScale(W, jac->diag, NULL));
238   PetscCall(MatTransposeMatMult(jac->Vt, W, MAT_REUSE_MATRIX, PETSC_DEFAULT, &Y));
239   PetscCall(MatDestroy(&W));
240   PetscFunctionReturn(0);
241 }
242 
243 static PetscErrorCode PCApplyTranspose_SVD(PC pc, Vec x, Vec y) {
244   PC_SVD *jac  = (PC_SVD *)pc->data;
245   Vec     work = jac->work, xred, yred;
246 
247   PetscFunctionBegin;
248   PetscCall(PCSVDGetVec(pc, PC_LEFT, READ, x, &xred));
249   PetscCall(PCSVDGetVec(pc, PC_RIGHT, WRITE, y, &yred));
250   PetscCall(MatMult(jac->Vt, xred, work));
251   PetscCall(VecPointwiseMult(work, work, jac->diag));
252   PetscCall(MatMult(jac->U, work, yred));
253   PetscCall(PCSVDRestoreVec(pc, PC_LEFT, READ, x, &xred));
254   PetscCall(PCSVDRestoreVec(pc, PC_RIGHT, WRITE, y, &yred));
255   PetscFunctionReturn(0);
256 }
257 
258 static PetscErrorCode PCReset_SVD(PC pc) {
259   PC_SVD *jac = (PC_SVD *)pc->data;
260 
261   PetscFunctionBegin;
262   PetscCall(MatDestroy(&jac->A));
263   PetscCall(MatDestroy(&jac->U));
264   PetscCall(MatDestroy(&jac->Vt));
265   PetscCall(VecDestroy(&jac->diag));
266   PetscCall(VecDestroy(&jac->work));
267   PetscCall(VecScatterDestroy(&jac->right2red));
268   PetscCall(VecScatterDestroy(&jac->left2red));
269   PetscCall(VecDestroy(&jac->rightred));
270   PetscCall(VecDestroy(&jac->leftred));
271   PetscFunctionReturn(0);
272 }
273 
274 /*
275    PCDestroy_SVD - Destroys the private context for the SVD preconditioner
276    that was created with PCCreate_SVD().
277 
278    Input Parameter:
279 .  pc - the preconditioner context
280 
281    Application Interface Routine: PCDestroy()
282 */
283 static PetscErrorCode PCDestroy_SVD(PC pc) {
284   PC_SVD *jac = (PC_SVD *)pc->data;
285 
286   PetscFunctionBegin;
287   PetscCall(PCReset_SVD(pc));
288   PetscCall(PetscViewerDestroy(&jac->monitor));
289   PetscCall(PetscFree(pc->data));
290   PetscFunctionReturn(0);
291 }
292 
293 static PetscErrorCode PCSetFromOptions_SVD(PC pc, PetscOptionItems *PetscOptionsObject) {
294   PC_SVD   *jac = (PC_SVD *)pc->data;
295   PetscBool flg;
296 
297   PetscFunctionBegin;
298   PetscOptionsHeadBegin(PetscOptionsObject, "SVD options");
299   PetscCall(PetscOptionsReal("-pc_svd_zero_sing", "Singular values smaller than this treated as zero", "None", jac->zerosing, &jac->zerosing, NULL));
300   PetscCall(PetscOptionsInt("-pc_svd_ess_rank", "Essential rank of operator (0 to use entire operator)", "None", jac->essrank, &jac->essrank, NULL));
301   PetscCall(PetscOptionsViewer("-pc_svd_monitor", "Monitor the conditioning, and extremal singular values", "None", &jac->monitor, &jac->monitorformat, &flg));
302   PetscOptionsHeadEnd();
303   PetscFunctionReturn(0);
304 }
305 
306 static PetscErrorCode PCView_SVD(PC pc, PetscViewer viewer) {
307   PC_SVD   *svd = (PC_SVD *)pc->data;
308   PetscBool iascii;
309 
310   PetscFunctionBegin;
311   PetscCall(PetscObjectTypeCompare((PetscObject)viewer, PETSCVIEWERASCII, &iascii));
312   if (iascii) {
313     PetscCall(PetscViewerASCIIPrintf(viewer, "  All singular values smaller than %g treated as zero\n", (double)svd->zerosing));
314     PetscCall(PetscViewerASCIIPrintf(viewer, "  Provided essential rank of the matrix %" PetscInt_FMT " (all other eigenvalues are zeroed)\n", svd->essrank));
315   }
316   PetscFunctionReturn(0);
317 }
318 
319 /*
320    PCCreate_SVD - Creates a SVD preconditioner context, PC_SVD,
321    and sets this as the private data within the generic preconditioning
322    context, PC, that was created within PCCreate().
323 
324    Input Parameter:
325 .  pc - the preconditioner context
326 
327    Application Interface Routine: PCCreate()
328 */
329 
330 /*MC
331      PCSVD - Use pseudo inverse defined by SVD of operator
332 
333    Level: advanced
334 
335   Options Database Keys:
336 +  -pc_svd_zero_sing <rtol> - Singular values smaller than this are treated as zero
337 -  -pc_svd_monitor - Print information on the extreme singular values of the operator
338 
339   Developer Note:
340   This implementation automatically creates a redundant copy of the
341    matrix on each process and uses a sequential SVD solve. Why does it do this instead
342    of using the composable `PCREDUNDANT` object?
343 
344 .seealso: `PCCreate()`, `PCSetType()`, `PCType`, `PC`, `PCREDUNDANT`
345 M*/
346 
347 PETSC_EXTERN PetscErrorCode PCCreate_SVD(PC pc) {
348   PC_SVD     *jac;
349   PetscMPIInt size = 0;
350 
351   PetscFunctionBegin;
352   /*
353      Creates the private data structure for this preconditioner and
354      attach it to the PC object.
355   */
356   PetscCall(PetscNew(&jac));
357   jac->zerosing = 1.e-12;
358   pc->data      = (void *)jac;
359 
360   /*
361       Set the pointers for the functions that are provided above.
362       Now when the user-level routines (such as PCApply(), PCDestroy(), etc.)
363       are called, they will automatically call these functions.  Note we
364       choose not to provide a couple of these functions since they are
365       not needed.
366   */
367 
368 #if defined(PETSC_HAVE_COMPLEX)
369   PetscCallMPI(MPI_Comm_size(PetscObjectComm((PetscObject)pc), &size));
370 #endif
371   if (size == 1) pc->ops->matapply = PCMatApply_SVD;
372   pc->ops->apply           = PCApply_SVD;
373   pc->ops->applytranspose  = PCApplyTranspose_SVD;
374   pc->ops->setup           = PCSetUp_SVD;
375   pc->ops->reset           = PCReset_SVD;
376   pc->ops->destroy         = PCDestroy_SVD;
377   pc->ops->setfromoptions  = PCSetFromOptions_SVD;
378   pc->ops->view            = PCView_SVD;
379   pc->ops->applyrichardson = NULL;
380   PetscFunctionReturn(0);
381 }
382