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