xref: /petsc/src/snes/interface/noise/snesmfj2.c (revision e0f5bfbec699682fa3e8b8532b1176849ea4e12a)
1 
2 #include <petsc/private/snesimpl.h> /*I  "petscsnes.h"   I*/
3 /* matimpl.h is needed only for logging of matrix operation */
4 #include <petsc/private/matimpl.h>
5 
6 PETSC_INTERN PetscErrorCode SNESUnSetMatrixFreeParameter(SNES);
7 
8 PETSC_INTERN PetscErrorCode SNESDiffParameterCreate_More(SNES, Vec, void **);
9 PETSC_INTERN PetscErrorCode SNESDiffParameterCompute_More(SNES, void *, Vec, Vec, PetscReal *, PetscReal *);
10 PETSC_INTERN PetscErrorCode SNESDiffParameterDestroy_More(void *);
11 
12 typedef struct {                 /* default context for matrix-free SNES */
13   SNES         snes;             /* SNES context */
14   Vec          w;                /* work vector */
15   MatNullSpace sp;               /* null space context */
16   PetscReal    error_rel;        /* square root of relative error in computing function */
17   PetscReal    umin;             /* minimum allowable u'a value relative to |u|_1 */
18   PetscBool    jorge;            /* flag indicating use of Jorge's method for determining the differencing parameter */
19   PetscReal    h;                /* differencing parameter */
20   PetscBool    need_h;           /* flag indicating whether we must compute h */
21   PetscBool    need_err;         /* flag indicating whether we must currently compute error_rel */
22   PetscBool    compute_err;      /* flag indicating whether we must ever compute error_rel */
23   PetscInt     compute_err_iter; /* last iter where we've computer error_rel */
24   PetscInt     compute_err_freq; /* frequency of computing error_rel */
25   void        *data;             /* implementation-specific data */
26 } MFCtx_Private;
27 
28 PetscErrorCode SNESMatrixFreeDestroy2_Private(Mat mat) {
29   MFCtx_Private *ctx;
30 
31   PetscFunctionBegin;
32   PetscCall(MatShellGetContext(mat, &ctx));
33   PetscCall(VecDestroy(&ctx->w));
34   PetscCall(MatNullSpaceDestroy(&ctx->sp));
35   if (ctx->jorge || ctx->compute_err) PetscCall(SNESDiffParameterDestroy_More(ctx->data));
36   PetscCall(PetscFree(ctx));
37   PetscFunctionReturn(0);
38 }
39 
40 /*
41    SNESMatrixFreeView2_Private - Views matrix-free parameters.
42  */
43 PetscErrorCode SNESMatrixFreeView2_Private(Mat J, PetscViewer viewer) {
44   MFCtx_Private *ctx;
45   PetscBool      iascii;
46 
47   PetscFunctionBegin;
48   PetscCall(MatShellGetContext(J, &ctx));
49   PetscCall(PetscObjectTypeCompare((PetscObject)viewer, PETSCVIEWERASCII, &iascii));
50   if (iascii) {
51     PetscCall(PetscViewerASCIIPrintf(viewer, "  SNES matrix-free approximation:\n"));
52     if (ctx->jorge) PetscCall(PetscViewerASCIIPrintf(viewer, "    using Jorge's method of determining differencing parameter\n"));
53     PetscCall(PetscViewerASCIIPrintf(viewer, "    err=%g (relative error in function evaluation)\n", (double)ctx->error_rel));
54     PetscCall(PetscViewerASCIIPrintf(viewer, "    umin=%g (minimum iterate parameter)\n", (double)ctx->umin));
55     if (ctx->compute_err) PetscCall(PetscViewerASCIIPrintf(viewer, "    freq_err=%" PetscInt_FMT " (frequency for computing err)\n", ctx->compute_err_freq));
56   }
57   PetscFunctionReturn(0);
58 }
59 
60 /*
61   SNESMatrixFreeMult2_Private - Default matrix-free form for Jacobian-vector
62   product, y = F'(u)*a:
63         y = (F(u + ha) - F(u)) /h,
64   where F = nonlinear function, as set by SNESSetFunction()
65         u = current iterate
66         h = difference interval
67 */
68 PetscErrorCode SNESMatrixFreeMult2_Private(Mat mat, Vec a, Vec y) {
69   MFCtx_Private *ctx;
70   SNES           snes;
71   PetscReal      h, norm, sum, umin, noise;
72   PetscScalar    hs, dot;
73   Vec            w, U, F;
74   MPI_Comm       comm;
75   PetscInt       iter;
76   PetscErrorCode (*eval_fct)(SNES, Vec, Vec);
77 
78   PetscFunctionBegin;
79   /* We log matrix-free matrix-vector products separately, so that we can
80      separate the performance monitoring from the cases that use conventional
81      storage.  We may eventually modify event logging to associate events
82      with particular objects, hence alleviating the more general problem. */
83   PetscCall(PetscLogEventBegin(MATMFFD_Mult, a, y, 0, 0));
84 
85   PetscCall(PetscObjectGetComm((PetscObject)mat, &comm));
86   PetscCall(MatShellGetContext(mat, &ctx));
87   snes = ctx->snes;
88   w    = ctx->w;
89   umin = ctx->umin;
90 
91   PetscCall(SNESGetSolution(snes, &U));
92   eval_fct = SNESComputeFunction;
93   PetscCall(SNESGetFunction(snes, &F, NULL, NULL));
94 
95   /* Determine a "good" step size, h */
96   if (ctx->need_h) {
97     /* Use Jorge's method to compute h */
98     if (ctx->jorge) {
99       PetscCall(SNESDiffParameterCompute_More(snes, ctx->data, U, a, &noise, &h));
100 
101       /* Use the Brown/Saad method to compute h */
102     } else {
103       /* Compute error if desired */
104       PetscCall(SNESGetIterationNumber(snes, &iter));
105       if ((ctx->need_err) || ((ctx->compute_err_freq) && (ctx->compute_err_iter != iter) && (!((iter - 1) % ctx->compute_err_freq)))) {
106         /* Use Jorge's method to compute noise */
107         PetscCall(SNESDiffParameterCompute_More(snes, ctx->data, U, a, &noise, &h));
108 
109         ctx->error_rel = PetscSqrtReal(noise);
110 
111         PetscCall(PetscInfo(snes, "Using Jorge's noise: noise=%g, sqrt(noise)=%g, h_more=%g\n", (double)noise, (double)ctx->error_rel, (double)h));
112 
113         ctx->compute_err_iter = iter;
114         ctx->need_err         = PETSC_FALSE;
115       }
116 
117       PetscCall(VecDotBegin(U, a, &dot));
118       PetscCall(VecNormBegin(a, NORM_1, &sum));
119       PetscCall(VecNormBegin(a, NORM_2, &norm));
120       PetscCall(VecDotEnd(U, a, &dot));
121       PetscCall(VecNormEnd(a, NORM_1, &sum));
122       PetscCall(VecNormEnd(a, NORM_2, &norm));
123 
124       /* Safeguard for step sizes too small */
125       if (sum == 0.0) {
126         dot  = 1.0;
127         norm = 1.0;
128       } else if (PetscAbsScalar(dot) < umin * sum && PetscRealPart(dot) >= 0.0) dot = umin * sum;
129       else if (PetscAbsScalar(dot) < 0.0 && PetscRealPart(dot) > -umin * sum) dot = -umin * sum;
130       h = PetscRealPart(ctx->error_rel * dot / (norm * norm));
131     }
132   } else h = ctx->h;
133 
134   if (!ctx->jorge || !ctx->need_h) PetscCall(PetscInfo(snes, "h = %g\n", (double)h));
135 
136   /* Evaluate function at F(u + ha) */
137   hs = h;
138   PetscCall(VecWAXPY(w, hs, a, U));
139   PetscCall(eval_fct(snes, w, y));
140   PetscCall(VecAXPY(y, -1.0, F));
141   PetscCall(VecScale(y, 1.0 / hs));
142   if (mat->nullsp) PetscCall(MatNullSpaceRemove(mat->nullsp, y));
143 
144   PetscCall(PetscLogEventEnd(MATMFFD_Mult, a, y, 0, 0));
145   PetscFunctionReturn(0);
146 }
147 
148 /*@C
149    MatCreateSNESMFMore - Creates a matrix-free matrix
150    context for use with a `SNES` solver that uses the More method to compute an optimal h based on the noise of the function.  This matrix can be used as
151    the Jacobian argument for the routine `SNESSetJacobian()`.
152 
153    Input Parameters:
154 +  snes - the `SNES` context
155 -  x - vector where `SNES` solution is to be stored.
156 
157    Output Parameter:
158 .  J - the matrix-free matrix
159 
160    Options Database Keys:
161 +  -snes_mf_err <error_rel> - see `MatCreateSNESMF()`
162 .  -snes_mf_umin <umin> - see `MatCreateSNESMF()`
163 .  -snes_mf_compute_err - compute the square root or relative error in function
164 .  -snes_mf_freq_err <freq> - set the frequency to recompute the parameters
165 -  -snes_mf_jorge - use the method of Jorge More
166 
167    Level: advanced
168 
169    Notes:
170    This is an experimental approach, use `MatCreateSNESMF()`.
171 
172    The matrix-free matrix context merely contains the function pointers
173    and work space for performing finite difference approximations of
174    Jacobian-vector products, J(u)*a, via
175 
176 $       J(u)*a = [J(u+h*a) - J(u)]/h,
177 $   where by default
178 $        h = error_rel*u'a/||a||^2                        if  |u'a| > umin*||a||_{1}
179 $          = error_rel*umin*sign(u'a)*||a||_{1}/||a||^2   otherwise
180 $   where
181 $        error_rel = square root of relative error in
182 $                    function evaluation
183 $        umin = minimum iterate parameter
184 $   Alternatively, the differencing parameter, h, can be set using
185 $   Jorge's nifty new strategy if one specifies the option
186 $          -snes_mf_jorge
187 
188    The user can set these parameters via `MatMFFDSetFunctionError()`.
189    See Users-Manual: ch_snes for details
190 
191    The user should call `MatDestroy()` when finished with the matrix-free
192    matrix context.
193 
194 .seealso: `SNESCreateMF`(), `MatCreateMFFD()`, `MatDestroy()`, `MatMFFDSetFunctionError()`
195 @*/
196 PetscErrorCode MatCreateSNESMFMore(SNES snes, Vec x, Mat *J) {
197   MPI_Comm       comm;
198   MFCtx_Private *mfctx;
199   PetscInt       n, nloc;
200   PetscBool      flg;
201   char           p[64];
202 
203   PetscFunctionBegin;
204   PetscCall(PetscNew(&mfctx));
205   mfctx->sp               = NULL;
206   mfctx->snes             = snes;
207   mfctx->error_rel        = PETSC_SQRT_MACHINE_EPSILON;
208   mfctx->umin             = 1.e-6;
209   mfctx->h                = 0.0;
210   mfctx->need_h           = PETSC_TRUE;
211   mfctx->need_err         = PETSC_FALSE;
212   mfctx->compute_err      = PETSC_FALSE;
213   mfctx->compute_err_freq = 0;
214   mfctx->compute_err_iter = -1;
215   mfctx->compute_err      = PETSC_FALSE;
216   mfctx->jorge            = PETSC_FALSE;
217 
218   PetscCall(PetscOptionsGetReal(((PetscObject)snes)->options, ((PetscObject)snes)->prefix, "-snes_mf_err", &mfctx->error_rel, NULL));
219   PetscCall(PetscOptionsGetReal(((PetscObject)snes)->options, ((PetscObject)snes)->prefix, "-snes_mf_umin", &mfctx->umin, NULL));
220   PetscCall(PetscOptionsGetBool(((PetscObject)snes)->options, ((PetscObject)snes)->prefix, "-snes_mf_jorge", &mfctx->jorge, NULL));
221   PetscCall(PetscOptionsGetBool(((PetscObject)snes)->options, ((PetscObject)snes)->prefix, "-snes_mf_compute_err", &mfctx->compute_err, NULL));
222   PetscCall(PetscOptionsGetInt(((PetscObject)snes)->options, ((PetscObject)snes)->prefix, "-snes_mf_freq_err", &mfctx->compute_err_freq, &flg));
223   if (flg) {
224     if (mfctx->compute_err_freq < 0) mfctx->compute_err_freq = 0;
225     mfctx->compute_err = PETSC_TRUE;
226   }
227   if (mfctx->compute_err) mfctx->need_err = PETSC_TRUE;
228   if (mfctx->jorge || mfctx->compute_err) {
229     PetscCall(SNESDiffParameterCreate_More(snes, x, &mfctx->data));
230   } else mfctx->data = NULL;
231 
232   PetscCall(PetscOptionsHasHelp(((PetscObject)snes)->options, &flg));
233   PetscCall(PetscStrncpy(p, "-", sizeof(p)));
234   if (((PetscObject)snes)->prefix) PetscCall(PetscStrlcat(p, ((PetscObject)snes)->prefix, sizeof(p)));
235   if (flg) {
236     PetscCall(PetscPrintf(PetscObjectComm((PetscObject)snes), " Matrix-free Options (via SNES):\n"));
237     PetscCall(PetscPrintf(PetscObjectComm((PetscObject)snes), "   %ssnes_mf_err <err>: set sqrt of relative error in function (default %g)\n", p, (double)mfctx->error_rel));
238     PetscCall(PetscPrintf(PetscObjectComm((PetscObject)snes), "   %ssnes_mf_umin <umin>: see users manual (default %g)\n", p, (double)mfctx->umin));
239     PetscCall(PetscPrintf(PetscObjectComm((PetscObject)snes), "   %ssnes_mf_jorge: use Jorge More's method\n", p));
240     PetscCall(PetscPrintf(PetscObjectComm((PetscObject)snes), "   %ssnes_mf_compute_err: compute sqrt or relative error in function\n", p));
241     PetscCall(PetscPrintf(PetscObjectComm((PetscObject)snes), "   %ssnes_mf_freq_err <freq>: frequency to recompute this (default only once)\n", p));
242     PetscCall(PetscPrintf(PetscObjectComm((PetscObject)snes), "   %ssnes_mf_noise_file <file>: set file for printing noise info\n", p));
243   }
244   PetscCall(VecDuplicate(x, &mfctx->w));
245   PetscCall(PetscObjectGetComm((PetscObject)x, &comm));
246   PetscCall(VecGetSize(x, &n));
247   PetscCall(VecGetLocalSize(x, &nloc));
248   PetscCall(MatCreate(comm, J));
249   PetscCall(MatSetSizes(*J, nloc, n, n, n));
250   PetscCall(MatSetType(*J, MATSHELL));
251   PetscCall(MatShellSetContext(*J, mfctx));
252   PetscCall(MatShellSetOperation(*J, MATOP_MULT, (void (*)(void))SNESMatrixFreeMult2_Private));
253   PetscCall(MatShellSetOperation(*J, MATOP_DESTROY, (void (*)(void))SNESMatrixFreeDestroy2_Private));
254   PetscCall(MatShellSetOperation(*J, MATOP_VIEW, (void (*)(void))SNESMatrixFreeView2_Private));
255   PetscCall(MatSetUp(*J));
256 
257   PetscFunctionReturn(0);
258 }
259 
260 /*@C
261    MatSNESMFMoreSetParameters - Sets the parameters for the approximation of
262    matrix-vector products using finite differences, see  `MatCreateSNESMFMore()`
263 
264    Input Parameters:
265 +  mat - the matrix
266 .  error_rel - relative error (should be set to the square root of the relative error in the function evaluations)
267 .  umin - minimum allowable u-value
268 -  h - differencing parameter
269 
270    Options Database Keys:
271 +  -snes_mf_err <error_rel> - see `MatCreateSNESMF()`
272 .  -snes_mf_umin <umin> - see `MatCreateSNESMF()`
273 .  -snes_mf_compute_err - compute the square root or relative error in function
274 .  -snes_mf_freq_err <freq> - set the frequency to recompute the parameters
275 -  -snes_mf_jorge - use the method of Jorge More
276 
277    Level: advanced
278 
279    Note:
280    If the user sets the parameter h directly, then this value will be used
281    instead of the default computation as discussed in `MatCreateSNESMFMore()`
282 
283 .seealso: `MatCreateSNESMF()`, `MatCreateSNESMFMore()`
284 @*/
285 PetscErrorCode MatSNESMFMoreSetParameters(Mat mat, PetscReal error, PetscReal umin, PetscReal h) {
286   MFCtx_Private *ctx;
287 
288   PetscFunctionBegin;
289   PetscCall(MatShellGetContext(mat, &ctx));
290   if (ctx) {
291     if (error != PETSC_DEFAULT) ctx->error_rel = error;
292     if (umin != PETSC_DEFAULT) ctx->umin = umin;
293     if (h != PETSC_DEFAULT) {
294       ctx->h      = h;
295       ctx->need_h = PETSC_FALSE;
296     }
297   }
298   PetscFunctionReturn(0);
299 }
300 
301 PetscErrorCode SNESUnSetMatrixFreeParameter(SNES snes) {
302   MFCtx_Private *ctx;
303   Mat            mat;
304 
305   PetscFunctionBegin;
306   PetscCall(SNESGetJacobian(snes, &mat, NULL, NULL, NULL));
307   PetscCall(MatShellGetContext(mat, &ctx));
308   if (ctx) ctx->need_h = PETSC_TRUE;
309   PetscFunctionReturn(0);
310 }
311