xref: /petsc/src/mat/interface/matnull.c (revision 0619917b5a674bb687c64e7daba2ab22be99af31)
1 
2 /*
3     Routines to project vectors out of null spaces.
4 */
5 
6 #include <petsc/private/matimpl.h> /*I "petscmat.h" I*/
7 
8 PetscClassId MAT_NULLSPACE_CLASSID;
9 
10 /*@C
11   MatNullSpaceSetFunction - set a function that removes a null space from a vector
12   out of null spaces.
13 
14   Logically Collective
15 
16   Input Parameters:
17 + sp  - the `MatNullSpace` null space object
18 . rem - the function that removes the null space
19 - ctx - context for the remove function
20 
21   Level: advanced
22 
23 .seealso: [](ch_matrices), `Mat`, `MatNullSpace`, `MatNullSpaceDestroy()`, `MatNullSpaceRemove()`, `MatSetNullSpace()`, `MatNullSpaceCreate()`
24 @*/
25 PetscErrorCode MatNullSpaceSetFunction(MatNullSpace sp, PetscErrorCode (*rem)(MatNullSpace, Vec, void *), void *ctx)
26 {
27   PetscFunctionBegin;
28   PetscValidHeaderSpecific(sp, MAT_NULLSPACE_CLASSID, 1);
29   sp->remove = rem;
30   sp->rmctx  = ctx;
31   PetscFunctionReturn(PETSC_SUCCESS);
32 }
33 
34 /*@C
35   MatNullSpaceGetVecs - get the vectors defining the null space
36 
37   Not Collective
38 
39   Input Parameter:
40 . sp - null space object
41 
42   Output Parameters:
43 + has_const - `PETSC_TRUE` if the null space contains the constant vector, otherwise `PETSC_FALSE`
44 . n         - number of vectors (excluding constant vector) in the null space
45 - vecs      - orthonormal vectors that span the null space (excluding the constant vector), `NULL` if `n` is 0
46 
47   Level: developer
48 
49   Note:
50   These vectors and the array are owned by the `MatNullSpace` and should not be destroyed or freeded by the caller
51 
52 .seealso: [](ch_matrices), `Mat`, `MatNullSpace`, `MatNullSpaceCreate()`, `MatGetNullSpace()`, `MatGetNearNullSpace()`
53 @*/
54 PetscErrorCode MatNullSpaceGetVecs(MatNullSpace sp, PetscBool *has_const, PetscInt *n, const Vec **vecs)
55 {
56   PetscFunctionBegin;
57   PetscValidHeaderSpecific(sp, MAT_NULLSPACE_CLASSID, 1);
58   if (has_const) *has_const = sp->has_cnst;
59   if (n) *n = sp->n;
60   if (vecs) *vecs = sp->vecs;
61   PetscFunctionReturn(PETSC_SUCCESS);
62 }
63 
64 /*@
65   MatNullSpaceCreateRigidBody - create rigid body modes from coordinates
66 
67   Collective
68 
69   Input Parameter:
70 . coords - block of coordinates of each node, must have block size set
71 
72   Output Parameter:
73 . sp - the null space
74 
75   Level: advanced
76 
77   Notes:
78   If you are solving an elasticity problem you should likely use this, in conjunction with `MatSetNearNullSpace()`, to provide information that
79   the `PCGAMG` preconditioner can use to construct a much more efficient preconditioner.
80 
81   If you are solving an elasticity problem with pure Neumann boundary conditions you can use this in conjunction with `MatSetNullSpace()` to
82   provide this information to the linear solver so it can handle the null space appropriately in the linear solution.
83 
84 .seealso: [](ch_matrices), `Mat`, `MatNullSpace`, `MatNullSpaceCreate()`, `MatSetNearNullSpace()`, `MatSetNullSpace()`, `PCGAMG`
85 @*/
86 PetscErrorCode MatNullSpaceCreateRigidBody(Vec coords, MatNullSpace *sp)
87 {
88   const PetscScalar *x;
89   PetscScalar       *v[6], dots[5];
90   Vec                vec[6];
91   PetscInt           n, N, dim, nmodes, i, j;
92   PetscReal          sN;
93 
94   PetscFunctionBegin;
95   PetscCall(VecGetBlockSize(coords, &dim));
96   PetscCall(VecGetLocalSize(coords, &n));
97   PetscCall(VecGetSize(coords, &N));
98   n /= dim;
99   N /= dim;
100   sN = 1. / PetscSqrtReal((PetscReal)N);
101   switch (dim) {
102   case 1:
103     PetscCall(MatNullSpaceCreate(PetscObjectComm((PetscObject)coords), PETSC_TRUE, 0, NULL, sp));
104     break;
105   case 2:
106   case 3:
107     nmodes = (dim == 2) ? 3 : 6;
108     PetscCall(VecCreate(PetscObjectComm((PetscObject)coords), &vec[0]));
109     PetscCall(VecSetSizes(vec[0], dim * n, dim * N));
110     PetscCall(VecSetBlockSize(vec[0], dim));
111     PetscCall(VecSetUp(vec[0]));
112     for (i = 1; i < nmodes; i++) PetscCall(VecDuplicate(vec[0], &vec[i]));
113     for (i = 0; i < nmodes; i++) PetscCall(VecGetArray(vec[i], &v[i]));
114     PetscCall(VecGetArrayRead(coords, &x));
115     for (i = 0; i < n; i++) {
116       if (dim == 2) {
117         v[0][i * 2 + 0] = sN;
118         v[0][i * 2 + 1] = 0.;
119         v[1][i * 2 + 0] = 0.;
120         v[1][i * 2 + 1] = sN;
121         /* Rotations */
122         v[2][i * 2 + 0] = -x[i * 2 + 1];
123         v[2][i * 2 + 1] = x[i * 2 + 0];
124       } else {
125         v[0][i * 3 + 0] = sN;
126         v[0][i * 3 + 1] = 0.;
127         v[0][i * 3 + 2] = 0.;
128         v[1][i * 3 + 0] = 0.;
129         v[1][i * 3 + 1] = sN;
130         v[1][i * 3 + 2] = 0.;
131         v[2][i * 3 + 0] = 0.;
132         v[2][i * 3 + 1] = 0.;
133         v[2][i * 3 + 2] = sN;
134 
135         v[3][i * 3 + 0] = x[i * 3 + 1];
136         v[3][i * 3 + 1] = -x[i * 3 + 0];
137         v[3][i * 3 + 2] = 0.;
138         v[4][i * 3 + 0] = 0.;
139         v[4][i * 3 + 1] = -x[i * 3 + 2];
140         v[4][i * 3 + 2] = x[i * 3 + 1];
141         v[5][i * 3 + 0] = x[i * 3 + 2];
142         v[5][i * 3 + 1] = 0.;
143         v[5][i * 3 + 2] = -x[i * 3 + 0];
144       }
145     }
146     for (i = 0; i < nmodes; i++) PetscCall(VecRestoreArray(vec[i], &v[i]));
147     PetscCall(VecRestoreArrayRead(coords, &x));
148     for (i = dim; i < nmodes; i++) {
149       /* Orthonormalize vec[i] against vec[0:i-1] */
150       PetscCall(VecMDot(vec[i], i, vec, dots));
151       for (j = 0; j < i; j++) dots[j] *= -1.;
152       PetscCall(VecMAXPY(vec[i], i, dots, vec));
153       PetscCall(VecNormalize(vec[i], NULL));
154     }
155     PetscCall(MatNullSpaceCreate(PetscObjectComm((PetscObject)coords), PETSC_FALSE, nmodes, vec, sp));
156     for (i = 0; i < nmodes; i++) PetscCall(VecDestroy(&vec[i]));
157   }
158   PetscFunctionReturn(PETSC_SUCCESS);
159 }
160 
161 /*@C
162   MatNullSpaceView - Visualizes a null space object.
163 
164   Collective; No Fortran Support
165 
166   Input Parameters:
167 + sp     - the null space
168 - viewer - visualization context
169 
170   Level: advanced
171 
172 .seealso: [](ch_matrices), `Mat`, `MatNullSpace`, `PetscViewer`, `MatNullSpaceCreate()`, `PetscViewerASCIIOpen()`
173 @*/
174 PetscErrorCode MatNullSpaceView(MatNullSpace sp, PetscViewer viewer)
175 {
176   PetscBool iascii;
177 
178   PetscFunctionBegin;
179   PetscValidHeaderSpecific(sp, MAT_NULLSPACE_CLASSID, 1);
180   if (!viewer) PetscCall(PetscViewerASCIIGetStdout(PetscObjectComm((PetscObject)sp), &viewer));
181   PetscValidHeaderSpecific(viewer, PETSC_VIEWER_CLASSID, 2);
182   PetscCheckSameComm(sp, 1, viewer, 2);
183 
184   PetscCall(PetscObjectTypeCompare((PetscObject)viewer, PETSCVIEWERASCII, &iascii));
185   if (iascii) {
186     PetscViewerFormat format;
187     PetscInt          i;
188     PetscCall(PetscViewerGetFormat(viewer, &format));
189     PetscCall(PetscObjectPrintClassNamePrefixType((PetscObject)sp, viewer));
190     PetscCall(PetscViewerASCIIPushTab(viewer));
191     PetscCall(PetscViewerASCIIPrintf(viewer, "Contains %" PetscInt_FMT " vector%s%s\n", sp->n, sp->n == 1 ? "" : "s", sp->has_cnst ? " and the constant" : ""));
192     if (sp->remove) PetscCall(PetscViewerASCIIPrintf(viewer, "Has user-provided removal function\n"));
193     if (!(format == PETSC_VIEWER_ASCII_INFO || format == PETSC_VIEWER_ASCII_INFO_DETAIL)) {
194       for (i = 0; i < sp->n; i++) PetscCall(VecView(sp->vecs[i], viewer));
195     }
196     PetscCall(PetscViewerASCIIPopTab(viewer));
197   }
198   PetscFunctionReturn(PETSC_SUCCESS);
199 }
200 
201 /*@C
202   MatNullSpaceCreate - Creates a `MatNullSpace` data structure used to project vectors out of null spaces.
203 
204   Collective
205 
206   Input Parameters:
207 + comm     - the MPI communicator associated with the object
208 . has_cnst - `PETSC_TRUE` if the null space contains the constant vector; otherwise `PETSC_FALSE`
209 . n        - number of vectors (excluding constant vector) in null space
210 - vecs     - the vectors that span the null space (excluding the constant vector);
211           these vectors must be orthonormal. These vectors are NOT copied, so do not change them
212           after this call. You should free the array that you pass in and destroy the vectors (this will reduce the reference count
213           for them by one).
214 
215   Output Parameter:
216 . SP - the null space context
217 
218   Level: advanced
219 
220   Notes:
221   See `MatNullSpaceSetFunction()` as an alternative way of providing the null space information instead of providing the vectors.
222 
223   If has_cnst is `PETSC_TRUE` you do not need to pass a constant vector in as a fourth argument to this routine, nor do you
224   need to pass in a function that eliminates the constant function into `MatNullSpaceSetFunction()`.
225 
226 .seealso: [](ch_matrices), `Mat`, `MatNullSpace`, `MatNullSpaceDestroy()`, `MatNullSpaceRemove()`, `MatSetNullSpace()`, `MatNullSpaceSetFunction()`
227 @*/
228 PetscErrorCode MatNullSpaceCreate(MPI_Comm comm, PetscBool has_cnst, PetscInt n, const Vec vecs[], MatNullSpace *SP)
229 {
230   MatNullSpace sp;
231   PetscInt     i;
232 
233   PetscFunctionBegin;
234   PetscCheck(n >= 0, PETSC_COMM_SELF, PETSC_ERR_ARG_OUTOFRANGE, "Number of vectors (given %" PetscInt_FMT ") cannot be negative", n);
235   if (n) PetscAssertPointer(vecs, 4);
236   for (i = 0; i < n; i++) PetscValidHeaderSpecific(vecs[i], VEC_CLASSID, 4);
237   PetscAssertPointer(SP, 5);
238   if (n) {
239     for (i = 0; i < n; i++) {
240       /* prevent the user from changes values in the vector */
241       PetscCall(VecLockReadPush(vecs[i]));
242     }
243   }
244   if (PetscUnlikelyDebug(n)) {
245     PetscScalar *dots;
246     for (i = 0; i < n; i++) {
247       PetscReal norm;
248       PetscCall(VecNorm(vecs[i], NORM_2, &norm));
249       PetscCheck(PetscAbsReal(norm - 1) <= PETSC_SQRT_MACHINE_EPSILON, PetscObjectComm((PetscObject)vecs[i]), PETSC_ERR_ARG_WRONG, "Vector %" PetscInt_FMT " must have 2-norm of 1.0, it is %g", i, (double)norm);
250     }
251     if (has_cnst) {
252       for (i = 0; i < n; i++) {
253         PetscScalar sum;
254         PetscCall(VecSum(vecs[i], &sum));
255         PetscCheck(PetscAbsScalar(sum) <= PETSC_SQRT_MACHINE_EPSILON, PetscObjectComm((PetscObject)vecs[i]), PETSC_ERR_ARG_WRONG, "Vector %" PetscInt_FMT " must be orthogonal to constant vector, inner product is %g", i, (double)PetscAbsScalar(sum));
256       }
257     }
258     PetscCall(PetscMalloc1(n - 1, &dots));
259     for (i = 0; i < n - 1; i++) {
260       PetscInt j;
261       PetscCall(VecMDot(vecs[i], n - i - 1, vecs + i + 1, dots));
262       for (j = 0; j < n - i - 1; j++) {
263         PetscCheck(PetscAbsScalar(dots[j]) <= PETSC_SQRT_MACHINE_EPSILON, PetscObjectComm((PetscObject)vecs[i]), PETSC_ERR_ARG_WRONG, "Vector %" PetscInt_FMT " must be orthogonal to vector %" PetscInt_FMT ", inner product is %g", i, i + j + 1, (double)PetscAbsScalar(dots[j]));
264       }
265     }
266     PetscCall(PetscFree(dots));
267   }
268 
269   *SP = NULL;
270   PetscCall(MatInitializePackage());
271 
272   PetscCall(PetscHeaderCreate(sp, MAT_NULLSPACE_CLASSID, "MatNullSpace", "Null space", "Mat", comm, MatNullSpaceDestroy, MatNullSpaceView));
273 
274   sp->has_cnst = has_cnst;
275   sp->n        = n;
276   sp->vecs     = NULL;
277   sp->alpha    = NULL;
278   sp->remove   = NULL;
279   sp->rmctx    = NULL;
280 
281   if (n) {
282     PetscCall(PetscMalloc1(n, &sp->vecs));
283     PetscCall(PetscMalloc1(n, &sp->alpha));
284     for (i = 0; i < n; i++) {
285       PetscCall(PetscObjectReference((PetscObject)vecs[i]));
286       sp->vecs[i] = vecs[i];
287     }
288   }
289 
290   *SP = sp;
291   PetscFunctionReturn(PETSC_SUCCESS);
292 }
293 
294 /*@
295   MatNullSpaceDestroy - Destroys a data structure used to project vectors out of null spaces.
296 
297   Collective
298 
299   Input Parameter:
300 . sp - the null space context to be destroyed
301 
302   Level: advanced
303 
304 .seealso: [](ch_matrices), `Mat`, `MatNullSpace`, `MatNullSpaceCreate()`, `MatNullSpaceRemove()`, `MatNullSpaceSetFunction()`
305 @*/
306 PetscErrorCode MatNullSpaceDestroy(MatNullSpace *sp)
307 {
308   PetscInt i;
309 
310   PetscFunctionBegin;
311   if (!*sp) PetscFunctionReturn(PETSC_SUCCESS);
312   PetscValidHeaderSpecific((*sp), MAT_NULLSPACE_CLASSID, 1);
313   if (--((PetscObject)(*sp))->refct > 0) {
314     *sp = NULL;
315     PetscFunctionReturn(PETSC_SUCCESS);
316   }
317 
318   for (i = 0; i < (*sp)->n; i++) PetscCall(VecLockReadPop((*sp)->vecs[i]));
319 
320   PetscCall(VecDestroyVecs((*sp)->n, &(*sp)->vecs));
321   PetscCall(PetscFree((*sp)->alpha));
322   PetscCall(PetscHeaderDestroy(sp));
323   PetscFunctionReturn(PETSC_SUCCESS);
324 }
325 
326 /*@C
327   MatNullSpaceRemove - Removes all the components of a null space from a vector.
328 
329   Collective
330 
331   Input Parameters:
332 + sp  - the null space context (if this is `NULL` then no null space is removed)
333 - vec - the vector from which the null space is to be removed
334 
335   Level: advanced
336 
337 .seealso: [](ch_matrices), `Mat`, `MatNullSpace`, `MatNullSpaceCreate()`, `MatNullSpaceDestroy()`, `MatNullSpaceSetFunction()`
338 @*/
339 PetscErrorCode MatNullSpaceRemove(MatNullSpace sp, Vec vec)
340 {
341   PetscScalar sum;
342   PetscInt    i, N;
343 
344   PetscFunctionBegin;
345   if (!sp) PetscFunctionReturn(PETSC_SUCCESS);
346   PetscValidHeaderSpecific(sp, MAT_NULLSPACE_CLASSID, 1);
347   PetscValidHeaderSpecific(vec, VEC_CLASSID, 2);
348 
349   if (sp->has_cnst) {
350     PetscCall(VecGetSize(vec, &N));
351     if (N > 0) {
352       PetscCall(VecSum(vec, &sum));
353       sum = sum / ((PetscScalar)(-1.0 * N));
354       PetscCall(VecShift(vec, sum));
355     }
356   }
357 
358   if (sp->n) {
359     PetscCall(VecMDot(vec, sp->n, sp->vecs, sp->alpha));
360     for (i = 0; i < sp->n; i++) sp->alpha[i] = -sp->alpha[i];
361     PetscCall(VecMAXPY(vec, sp->n, sp->alpha, sp->vecs));
362   }
363 
364   if (sp->remove) PetscCall((*sp->remove)(sp, vec, sp->rmctx));
365   PetscFunctionReturn(PETSC_SUCCESS);
366 }
367 
368 /*@
369   MatNullSpaceTest  - Tests if the claimed null space is really a null space of a matrix
370 
371   Collective
372 
373   Input Parameters:
374 + sp  - the null space context
375 - mat - the matrix
376 
377   Output Parameter:
378 . isNull - `PETSC_TRUE` if the nullspace is valid for this matrix
379 
380   Level: advanced
381 
382 .seealso: [](ch_matrices), `Mat`, `MatNullSpace`, `MatNullSpaceCreate()`, `MatNullSpaceDestroy()`, `MatNullSpaceSetFunction()`
383 @*/
384 PetscErrorCode MatNullSpaceTest(MatNullSpace sp, Mat mat, PetscBool *isNull)
385 {
386   PetscScalar sum;
387   PetscReal   nrm, tol = 10. * PETSC_SQRT_MACHINE_EPSILON;
388   PetscInt    j, n, N;
389   Vec         l, r;
390   PetscBool   flg1 = PETSC_FALSE, flg2 = PETSC_FALSE, consistent = PETSC_TRUE;
391   PetscViewer viewer;
392 
393   PetscFunctionBegin;
394   PetscValidHeaderSpecific(sp, MAT_NULLSPACE_CLASSID, 1);
395   PetscValidHeaderSpecific(mat, MAT_CLASSID, 2);
396   n = sp->n;
397   PetscCall(PetscOptionsGetBool(((PetscObject)sp)->options, ((PetscObject)mat)->prefix, "-mat_null_space_test_view", &flg1, NULL));
398   PetscCall(PetscOptionsGetBool(((PetscObject)sp)->options, ((PetscObject)mat)->prefix, "-mat_null_space_test_view_draw", &flg2, NULL));
399 
400   if (n) {
401     PetscCall(VecDuplicate(sp->vecs[0], &l));
402   } else {
403     PetscCall(MatCreateVecs(mat, &l, NULL));
404   }
405 
406   PetscCall(PetscViewerASCIIGetStdout(PetscObjectComm((PetscObject)sp), &viewer));
407   if (sp->has_cnst) {
408     PetscCall(VecDuplicate(l, &r));
409     PetscCall(VecGetSize(l, &N));
410     sum = 1.0 / PetscSqrtReal(N);
411     PetscCall(VecSet(l, sum));
412     PetscCall(MatMult(mat, l, r));
413     PetscCall(VecNorm(r, NORM_2, &nrm));
414     if (nrm >= tol) consistent = PETSC_FALSE;
415     if (flg1) {
416       if (consistent) {
417         PetscCall(PetscPrintf(PetscObjectComm((PetscObject)sp), "Constants are likely null vector"));
418       } else {
419         PetscCall(PetscPrintf(PetscObjectComm((PetscObject)sp), "Constants are unlikely null vector "));
420       }
421       PetscCall(PetscPrintf(PetscObjectComm((PetscObject)sp), "|| A * 1/N || = %g\n", (double)nrm));
422     }
423     if (!consistent && flg1) PetscCall(VecView(r, viewer));
424     if (!consistent && flg2) PetscCall(VecView(r, viewer));
425     PetscCall(VecDestroy(&r));
426   }
427 
428   for (j = 0; j < n; j++) {
429     PetscCall((*mat->ops->mult)(mat, sp->vecs[j], l));
430     PetscCall(VecNorm(l, NORM_2, &nrm));
431     if (nrm >= tol) consistent = PETSC_FALSE;
432     if (flg1) {
433       if (consistent) {
434         PetscCall(PetscPrintf(PetscObjectComm((PetscObject)sp), "Null vector %" PetscInt_FMT " is likely null vector", j));
435       } else {
436         PetscCall(PetscPrintf(PetscObjectComm((PetscObject)sp), "Null vector %" PetscInt_FMT " unlikely null vector ", j));
437         consistent = PETSC_FALSE;
438       }
439       PetscCall(PetscPrintf(PetscObjectComm((PetscObject)sp), "|| A * v[%" PetscInt_FMT "] || = %g\n", j, (double)nrm));
440     }
441     if (!consistent && flg1) PetscCall(VecView(l, viewer));
442     if (!consistent && flg2) PetscCall(VecView(l, viewer));
443   }
444 
445   PetscCheck(!sp->remove, PetscObjectComm((PetscObject)mat), PETSC_ERR_SUP, "Cannot test a null space provided as a function with MatNullSpaceSetFunction()");
446   PetscCall(VecDestroy(&l));
447   if (isNull) *isNull = consistent;
448   PetscFunctionReturn(PETSC_SUCCESS);
449 }
450