xref: /petsc/src/mat/interface/matnull.c (revision 5520554388890bd89a1c1cf7870aedf4e71d512f)
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     PetscCall(PetscLogObjectMemory((PetscObject)sp, n * (sizeof(Vec) + sizeof(PetscScalar))));
281     for (i = 0; i < n; i++) {
282       PetscCall(PetscObjectReference((PetscObject)vecs[i]));
283       sp->vecs[i] = vecs[i];
284     }
285   }
286 
287   *SP = sp;
288   PetscFunctionReturn(0);
289 }
290 
291 /*@
292    MatNullSpaceDestroy - Destroys a data structure used to project vectors out of null spaces.
293 
294    Collective on sp
295 
296    Input Parameter:
297 .  sp - the null space context to be destroyed
298 
299    Level: advanced
300 
301 .seealso: `MatNullSpace`, `MatNullSpaceCreate()`, `MatNullSpaceRemove()`, `MatNullSpaceSetFunction()`
302 @*/
303 PetscErrorCode MatNullSpaceDestroy(MatNullSpace *sp) {
304   PetscInt i;
305 
306   PetscFunctionBegin;
307   if (!*sp) PetscFunctionReturn(0);
308   PetscValidHeaderSpecific((*sp), MAT_NULLSPACE_CLASSID, 1);
309   if (--((PetscObject)(*sp))->refct > 0) {
310     *sp = NULL;
311     PetscFunctionReturn(0);
312   }
313 
314   for (i = 0; i < (*sp)->n; i++) PetscCall(VecLockReadPop((*sp)->vecs[i]));
315 
316   PetscCall(VecDestroyVecs((*sp)->n, &(*sp)->vecs));
317   PetscCall(PetscFree((*sp)->alpha));
318   PetscCall(PetscHeaderDestroy(sp));
319   PetscFunctionReturn(0);
320 }
321 
322 /*@C
323    MatNullSpaceRemove - Removes all the components of a null space from a vector.
324 
325    Collective on sp
326 
327    Input Parameters:
328 +  sp - the null space context (if this is NULL then no null space is removed)
329 -  vec - the vector from which the null space is to be removed
330 
331    Level: advanced
332 
333 .seealso: `MatNullSpace`, `MatNullSpaceCreate()`, `MatNullSpaceDestroy()`, `MatNullSpaceSetFunction()`
334 @*/
335 PetscErrorCode MatNullSpaceRemove(MatNullSpace sp, Vec vec) {
336   PetscScalar sum;
337   PetscInt    i, N;
338 
339   PetscFunctionBegin;
340   if (!sp) PetscFunctionReturn(0);
341   PetscValidHeaderSpecific(sp, MAT_NULLSPACE_CLASSID, 1);
342   PetscValidHeaderSpecific(vec, VEC_CLASSID, 2);
343 
344   if (sp->has_cnst) {
345     PetscCall(VecGetSize(vec, &N));
346     if (N > 0) {
347       PetscCall(VecSum(vec, &sum));
348       sum = sum / ((PetscScalar)(-1.0 * N));
349       PetscCall(VecShift(vec, sum));
350     }
351   }
352 
353   if (sp->n) {
354     PetscCall(VecMDot(vec, sp->n, sp->vecs, sp->alpha));
355     for (i = 0; i < sp->n; i++) sp->alpha[i] = -sp->alpha[i];
356     PetscCall(VecMAXPY(vec, sp->n, sp->alpha, sp->vecs));
357   }
358 
359   if (sp->remove) PetscCall((*sp->remove)(sp, vec, sp->rmctx));
360   PetscFunctionReturn(0);
361 }
362 
363 /*@
364    MatNullSpaceTest  - Tests if the claimed null space is really a null space of a matrix
365 
366    Collective on sp
367 
368    Input Parameters:
369 +  sp - the null space context
370 -  mat - the matrix
371 
372    Output Parameters:
373 .  isNull - `PETSC_TRUE` if the nullspace is valid for this matrix
374 
375    Level: advanced
376 
377 .seealso: `MatNullSpace`, `MatNullSpaceCreate()`, `MatNullSpaceDestroy()`, `MatNullSpaceSetFunction()`
378 @*/
379 PetscErrorCode MatNullSpaceTest(MatNullSpace sp, Mat mat, PetscBool *isNull) {
380   PetscScalar sum;
381   PetscReal   nrm, tol = 10. * PETSC_SQRT_MACHINE_EPSILON;
382   PetscInt    j, n, N;
383   Vec         l, r;
384   PetscBool   flg1 = PETSC_FALSE, flg2 = PETSC_FALSE, consistent = PETSC_TRUE;
385   PetscViewer viewer;
386 
387   PetscFunctionBegin;
388   PetscValidHeaderSpecific(sp, MAT_NULLSPACE_CLASSID, 1);
389   PetscValidHeaderSpecific(mat, MAT_CLASSID, 2);
390   n = sp->n;
391   PetscCall(PetscOptionsGetBool(((PetscObject)sp)->options, ((PetscObject)mat)->prefix, "-mat_null_space_test_view", &flg1, NULL));
392   PetscCall(PetscOptionsGetBool(((PetscObject)sp)->options, ((PetscObject)mat)->prefix, "-mat_null_space_test_view_draw", &flg2, NULL));
393 
394   if (n) {
395     PetscCall(VecDuplicate(sp->vecs[0], &l));
396   } else {
397     PetscCall(MatCreateVecs(mat, &l, NULL));
398   }
399 
400   PetscCall(PetscViewerASCIIGetStdout(PetscObjectComm((PetscObject)sp), &viewer));
401   if (sp->has_cnst) {
402     PetscCall(VecDuplicate(l, &r));
403     PetscCall(VecGetSize(l, &N));
404     sum = 1.0 / PetscSqrtReal(N);
405     PetscCall(VecSet(l, sum));
406     PetscCall(MatMult(mat, l, r));
407     PetscCall(VecNorm(r, NORM_2, &nrm));
408     if (nrm >= tol) consistent = PETSC_FALSE;
409     if (flg1) {
410       if (consistent) {
411         PetscCall(PetscPrintf(PetscObjectComm((PetscObject)sp), "Constants are likely null vector"));
412       } else {
413         PetscCall(PetscPrintf(PetscObjectComm((PetscObject)sp), "Constants are unlikely null vector "));
414       }
415       PetscCall(PetscPrintf(PetscObjectComm((PetscObject)sp), "|| A * 1/N || = %g\n", (double)nrm));
416     }
417     if (!consistent && flg1) PetscCall(VecView(r, viewer));
418     if (!consistent && flg2) PetscCall(VecView(r, viewer));
419     PetscCall(VecDestroy(&r));
420   }
421 
422   for (j = 0; j < n; j++) {
423     PetscCall((*mat->ops->mult)(mat, sp->vecs[j], l));
424     PetscCall(VecNorm(l, NORM_2, &nrm));
425     if (nrm >= tol) consistent = PETSC_FALSE;
426     if (flg1) {
427       if (consistent) {
428         PetscCall(PetscPrintf(PetscObjectComm((PetscObject)sp), "Null vector %" PetscInt_FMT " is likely null vector", j));
429       } else {
430         PetscCall(PetscPrintf(PetscObjectComm((PetscObject)sp), "Null vector %" PetscInt_FMT " unlikely null vector ", j));
431         consistent = PETSC_FALSE;
432       }
433       PetscCall(PetscPrintf(PetscObjectComm((PetscObject)sp), "|| A * v[%" PetscInt_FMT "] || = %g\n", j, (double)nrm));
434     }
435     if (!consistent && flg1) PetscCall(VecView(l, viewer));
436     if (!consistent && flg2) PetscCall(VecView(l, viewer));
437   }
438 
439   PetscCheck(!sp->remove, PetscObjectComm((PetscObject)mat), PETSC_ERR_SUP, "Cannot test a null space provided as a function with MatNullSpaceSetFunction()");
440   PetscCall(VecDestroy(&l));
441   if (isNull) *isNull = consistent;
442   PetscFunctionReturn(0);
443 }
444