xref: /petsc/src/mat/interface/matnull.c (revision d5b43468fb8780a8feea140ccd6fa3e6a50411cc)
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 {
27   PetscFunctionBegin;
28   PetscValidHeaderSpecific(sp, MAT_NULLSPACE_CLASSID, 1);
29   sp->remove = rem;
30   sp->rmctx  = ctx;
31   PetscFunctionReturn(0);
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_cnst - `PETSC_TRUE` if the null space contains the constant vector, otherwise `PETSC_FALSE`
44 .  n - number of vectors (excluding constant vector) in null space
45 -  vecs - orthonormal vectors that span the null space (excluding the constant vector)
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: `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(0);
62 }
63 
64 /*@
65    MatNullSpaceCreateRigidBody - create rigid body modes from coordinates
66 
67    Collective on coords
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: `MatNullSpace`, `MatNullSpaceCreate()`, `MatSetNearNullSpace()`, `MatSetNullSpace()`
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(0);
159 }
160 
161 /*@C
162    MatNullSpaceView - Visualizes a null space object.
163 
164    Collective on sp
165 
166    Input Parameters:
167 +  matnull - the null space
168 -  viewer - visualization context
169 
170    Level: advanced
171 
172    Fortran Note:
173    This routine is not supported in Fortran.
174 
175 .seealso: `MatNullSpace`, `MatNullSpaceCreate()`, `PetscViewerASCIIOpen()`
176 @*/
177 PetscErrorCode MatNullSpaceView(MatNullSpace sp, PetscViewer viewer)
178 {
179   PetscBool iascii;
180 
181   PetscFunctionBegin;
182   PetscValidHeaderSpecific(sp, MAT_NULLSPACE_CLASSID, 1);
183   if (!viewer) PetscCall(PetscViewerASCIIGetStdout(PetscObjectComm((PetscObject)sp), &viewer));
184   PetscValidHeaderSpecific(viewer, PETSC_VIEWER_CLASSID, 2);
185   PetscCheckSameComm(sp, 1, viewer, 2);
186 
187   PetscCall(PetscObjectTypeCompare((PetscObject)viewer, PETSCVIEWERASCII, &iascii));
188   if (iascii) {
189     PetscViewerFormat format;
190     PetscInt          i;
191     PetscCall(PetscViewerGetFormat(viewer, &format));
192     PetscCall(PetscObjectPrintClassNamePrefixType((PetscObject)sp, viewer));
193     PetscCall(PetscViewerASCIIPushTab(viewer));
194     PetscCall(PetscViewerASCIIPrintf(viewer, "Contains %" PetscInt_FMT " vector%s%s\n", sp->n, sp->n == 1 ? "" : "s", sp->has_cnst ? " and the constant" : ""));
195     if (sp->remove) PetscCall(PetscViewerASCIIPrintf(viewer, "Has user-provided removal function\n"));
196     if (!(format == PETSC_VIEWER_ASCII_INFO || format == PETSC_VIEWER_ASCII_INFO_DETAIL)) {
197       for (i = 0; i < sp->n; i++) PetscCall(VecView(sp->vecs[i], viewer));
198     }
199     PetscCall(PetscViewerASCIIPopTab(viewer));
200   }
201   PetscFunctionReturn(0);
202 }
203 
204 /*@C
205    MatNullSpaceCreate - Creates a `MatNullSpace` data structure used to project vectors out of null spaces.
206 
207    Collective
208 
209    Input Parameters:
210 +  comm - the MPI communicator associated with the object
211 .  has_cnst - `PETSC_TRUE` if the null space contains the constant vector; otherwise `PETSC_FALSE`
212 .  n - number of vectors (excluding constant vector) in null space
213 -  vecs - the vectors that span the null space (excluding the constant vector);
214           these vectors must be orthonormal. These vectors are NOT copied, so do not change them
215           after this call. You should free the array that you pass in and destroy the vectors (this will reduce the reference count
216           for them by one).
217 
218    Output Parameter:
219 .  SP - the null space context
220 
221    Level: advanced
222 
223    Notes:
224     See `MatNullSpaceSetFunction()` as an alternative way of providing the null space information instead of setting vecs.
225 
226     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
227     need to pass in a function that eliminates the constant function into `MatNullSpaceSetFunction()`.
228 
229 .seealso: `MatNullSpace`, `MatNullSpaceDestroy()`, `MatNullSpaceRemove()`, `MatSetNullSpace()`, `MatNullSpace`, `MatNullSpaceSetFunction()`
230 @*/
231 PetscErrorCode MatNullSpaceCreate(MPI_Comm comm, PetscBool has_cnst, PetscInt n, const Vec vecs[], MatNullSpace *SP)
232 {
233   MatNullSpace sp;
234   PetscInt     i;
235 
236   PetscFunctionBegin;
237   PetscCheck(n >= 0, PETSC_COMM_SELF, PETSC_ERR_ARG_OUTOFRANGE, "Number of vectors (given %" PetscInt_FMT ") cannot be negative", n);
238   if (n) PetscValidPointer(vecs, 4);
239   for (i = 0; i < n; i++) PetscValidHeaderSpecific(vecs[i], VEC_CLASSID, 4);
240   PetscValidPointer(SP, 5);
241   if (n) {
242     for (i = 0; i < n; i++) {
243       /* prevent the user from changes values in the vector */
244       PetscCall(VecLockReadPush(vecs[i]));
245     }
246   }
247   if (PetscUnlikelyDebug(n)) {
248     PetscScalar *dots;
249     for (i = 0; i < n; i++) {
250       PetscReal norm;
251       PetscCall(VecNorm(vecs[i], NORM_2, &norm));
252       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);
253     }
254     if (has_cnst) {
255       for (i = 0; i < n; i++) {
256         PetscScalar sum;
257         PetscCall(VecSum(vecs[i], &sum));
258         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));
259       }
260     }
261     PetscCall(PetscMalloc1(n - 1, &dots));
262     for (i = 0; i < n - 1; i++) {
263       PetscInt j;
264       PetscCall(VecMDot(vecs[i], n - i - 1, vecs + i + 1, dots));
265       for (j = 0; j < n - i - 1; j++) {
266         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]));
267       }
268     }
269     PetscCall(PetscFree(dots));
270   }
271 
272   *SP = NULL;
273   PetscCall(MatInitializePackage());
274 
275   PetscCall(PetscHeaderCreate(sp, MAT_NULLSPACE_CLASSID, "MatNullSpace", "Null space", "Mat", comm, MatNullSpaceDestroy, MatNullSpaceView));
276 
277   sp->has_cnst = has_cnst;
278   sp->n        = n;
279   sp->vecs     = NULL;
280   sp->alpha    = NULL;
281   sp->remove   = NULL;
282   sp->rmctx    = NULL;
283 
284   if (n) {
285     PetscCall(PetscMalloc1(n, &sp->vecs));
286     PetscCall(PetscMalloc1(n, &sp->alpha));
287     for (i = 0; i < n; i++) {
288       PetscCall(PetscObjectReference((PetscObject)vecs[i]));
289       sp->vecs[i] = vecs[i];
290     }
291   }
292 
293   *SP = sp;
294   PetscFunctionReturn(0);
295 }
296 
297 /*@
298    MatNullSpaceDestroy - Destroys a data structure used to project vectors out of null spaces.
299 
300    Collective on sp
301 
302    Input Parameter:
303 .  sp - the null space context to be destroyed
304 
305    Level: advanced
306 
307 .seealso: `MatNullSpace`, `MatNullSpaceCreate()`, `MatNullSpaceRemove()`, `MatNullSpaceSetFunction()`
308 @*/
309 PetscErrorCode MatNullSpaceDestroy(MatNullSpace *sp)
310 {
311   PetscInt i;
312 
313   PetscFunctionBegin;
314   if (!*sp) PetscFunctionReturn(0);
315   PetscValidHeaderSpecific((*sp), MAT_NULLSPACE_CLASSID, 1);
316   if (--((PetscObject)(*sp))->refct > 0) {
317     *sp = NULL;
318     PetscFunctionReturn(0);
319   }
320 
321   for (i = 0; i < (*sp)->n; i++) PetscCall(VecLockReadPop((*sp)->vecs[i]));
322 
323   PetscCall(VecDestroyVecs((*sp)->n, &(*sp)->vecs));
324   PetscCall(PetscFree((*sp)->alpha));
325   PetscCall(PetscHeaderDestroy(sp));
326   PetscFunctionReturn(0);
327 }
328 
329 /*@C
330    MatNullSpaceRemove - Removes all the components of a null space from a vector.
331 
332    Collective on sp
333 
334    Input Parameters:
335 +  sp - the null space context (if this is NULL then no null space is removed)
336 -  vec - the vector from which the null space is to be removed
337 
338    Level: advanced
339 
340 .seealso: `MatNullSpace`, `MatNullSpaceCreate()`, `MatNullSpaceDestroy()`, `MatNullSpaceSetFunction()`
341 @*/
342 PetscErrorCode MatNullSpaceRemove(MatNullSpace sp, Vec vec)
343 {
344   PetscScalar sum;
345   PetscInt    i, N;
346 
347   PetscFunctionBegin;
348   if (!sp) PetscFunctionReturn(0);
349   PetscValidHeaderSpecific(sp, MAT_NULLSPACE_CLASSID, 1);
350   PetscValidHeaderSpecific(vec, VEC_CLASSID, 2);
351 
352   if (sp->has_cnst) {
353     PetscCall(VecGetSize(vec, &N));
354     if (N > 0) {
355       PetscCall(VecSum(vec, &sum));
356       sum = sum / ((PetscScalar)(-1.0 * N));
357       PetscCall(VecShift(vec, sum));
358     }
359   }
360 
361   if (sp->n) {
362     PetscCall(VecMDot(vec, sp->n, sp->vecs, sp->alpha));
363     for (i = 0; i < sp->n; i++) sp->alpha[i] = -sp->alpha[i];
364     PetscCall(VecMAXPY(vec, sp->n, sp->alpha, sp->vecs));
365   }
366 
367   if (sp->remove) PetscCall((*sp->remove)(sp, vec, sp->rmctx));
368   PetscFunctionReturn(0);
369 }
370 
371 /*@
372    MatNullSpaceTest  - Tests if the claimed null space is really a null space of a matrix
373 
374    Collective on sp
375 
376    Input Parameters:
377 +  sp - the null space context
378 -  mat - the matrix
379 
380    Output Parameters:
381 .  isNull - `PETSC_TRUE` if the nullspace is valid for this matrix
382 
383    Level: advanced
384 
385 .seealso: `MatNullSpace`, `MatNullSpaceCreate()`, `MatNullSpaceDestroy()`, `MatNullSpaceSetFunction()`
386 @*/
387 PetscErrorCode MatNullSpaceTest(MatNullSpace sp, Mat mat, PetscBool *isNull)
388 {
389   PetscScalar sum;
390   PetscReal   nrm, tol = 10. * PETSC_SQRT_MACHINE_EPSILON;
391   PetscInt    j, n, N;
392   Vec         l, r;
393   PetscBool   flg1 = PETSC_FALSE, flg2 = PETSC_FALSE, consistent = PETSC_TRUE;
394   PetscViewer viewer;
395 
396   PetscFunctionBegin;
397   PetscValidHeaderSpecific(sp, MAT_NULLSPACE_CLASSID, 1);
398   PetscValidHeaderSpecific(mat, MAT_CLASSID, 2);
399   n = sp->n;
400   PetscCall(PetscOptionsGetBool(((PetscObject)sp)->options, ((PetscObject)mat)->prefix, "-mat_null_space_test_view", &flg1, NULL));
401   PetscCall(PetscOptionsGetBool(((PetscObject)sp)->options, ((PetscObject)mat)->prefix, "-mat_null_space_test_view_draw", &flg2, NULL));
402 
403   if (n) {
404     PetscCall(VecDuplicate(sp->vecs[0], &l));
405   } else {
406     PetscCall(MatCreateVecs(mat, &l, NULL));
407   }
408 
409   PetscCall(PetscViewerASCIIGetStdout(PetscObjectComm((PetscObject)sp), &viewer));
410   if (sp->has_cnst) {
411     PetscCall(VecDuplicate(l, &r));
412     PetscCall(VecGetSize(l, &N));
413     sum = 1.0 / PetscSqrtReal(N);
414     PetscCall(VecSet(l, sum));
415     PetscCall(MatMult(mat, l, r));
416     PetscCall(VecNorm(r, NORM_2, &nrm));
417     if (nrm >= tol) consistent = PETSC_FALSE;
418     if (flg1) {
419       if (consistent) {
420         PetscCall(PetscPrintf(PetscObjectComm((PetscObject)sp), "Constants are likely null vector"));
421       } else {
422         PetscCall(PetscPrintf(PetscObjectComm((PetscObject)sp), "Constants are unlikely null vector "));
423       }
424       PetscCall(PetscPrintf(PetscObjectComm((PetscObject)sp), "|| A * 1/N || = %g\n", (double)nrm));
425     }
426     if (!consistent && flg1) PetscCall(VecView(r, viewer));
427     if (!consistent && flg2) PetscCall(VecView(r, viewer));
428     PetscCall(VecDestroy(&r));
429   }
430 
431   for (j = 0; j < n; j++) {
432     PetscCall((*mat->ops->mult)(mat, sp->vecs[j], l));
433     PetscCall(VecNorm(l, NORM_2, &nrm));
434     if (nrm >= tol) consistent = PETSC_FALSE;
435     if (flg1) {
436       if (consistent) {
437         PetscCall(PetscPrintf(PetscObjectComm((PetscObject)sp), "Null vector %" PetscInt_FMT " is likely null vector", j));
438       } else {
439         PetscCall(PetscPrintf(PetscObjectComm((PetscObject)sp), "Null vector %" PetscInt_FMT " unlikely null vector ", j));
440         consistent = PETSC_FALSE;
441       }
442       PetscCall(PetscPrintf(PetscObjectComm((PetscObject)sp), "|| A * v[%" PetscInt_FMT "] || = %g\n", j, (double)nrm));
443     }
444     if (!consistent && flg1) PetscCall(VecView(l, viewer));
445     if (!consistent && flg2) PetscCall(VecView(l, viewer));
446   }
447 
448   PetscCheck(!sp->remove, PetscObjectComm((PetscObject)mat), PETSC_ERR_SUP, "Cannot test a null space provided as a function with MatNullSpaceSetFunction()");
449   PetscCall(VecDestroy(&l));
450   if (isNull) *isNull = consistent;
451   PetscFunctionReturn(0);
452 }
453