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