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