Actual source code: tsevent.c

  1: #include <petsc/private/tsimpl.h>

  3: /*
  4:   Fills array sign[] with signs of array f[]. If abs(f[i]) < vtol[i], the zero sign is taken.
  5:   All arrays should have length 'nev'
  6: */
  7: static inline void TSEventCalcSigns(PetscInt nev, const PetscReal *f, const PetscReal *vtol, PetscInt *sign)
  8: {
  9:   for (PetscInt i = 0; i < nev; i++) {
 10:     if (PetscAbsReal(f[i]) < vtol[i]) sign[i] = 0;
 11:     else sign[i] = PetscSign(f[i]);
 12:   }
 13: }

 15: /*
 16:   TSEventInitialize - Initializes TSEvent for TSSolve
 17: */
 18: PetscErrorCode TSEventInitialize(TSEvent event, TS ts, PetscReal t, Vec U)
 19: {
 20:   PetscFunctionBegin;
 21:   if (!event) PetscFunctionReturn(PETSC_SUCCESS);
 22:   PetscAssertPointer(event, 1);
 25:   event->ptime_prev    = t;
 26:   event->iterctr       = 0;
 27:   event->processing    = PETSC_FALSE;
 28:   event->revisit_right = PETSC_FALSE;
 29:   PetscCallBack("TSEvent indicator", (*event->indicator)(ts, t, U, event->fvalue_prev, event->ctx));
 30:   TSEventCalcSigns(event->nevents, event->fvalue_prev, event->vtol, event->fsign_prev); // by this time event->vtol should have been defined
 31:   PetscFunctionReturn(PETSC_SUCCESS);
 32: }

 34: PetscErrorCode TSEventDestroy(TSEvent *event)
 35: {
 36:   PetscFunctionBegin;
 37:   PetscAssertPointer(event, 1);
 38:   if (!*event) PetscFunctionReturn(PETSC_SUCCESS);
 39:   if (--(*event)->refct > 0) {
 40:     *event = NULL;
 41:     PetscFunctionReturn(PETSC_SUCCESS);
 42:   }

 44:   PetscCall(PetscFree((*event)->fvalue_prev));
 45:   PetscCall(PetscFree((*event)->fvalue));
 46:   PetscCall(PetscFree((*event)->fvalue_right));
 47:   PetscCall(PetscFree((*event)->fsign_prev));
 48:   PetscCall(PetscFree((*event)->fsign));
 49:   PetscCall(PetscFree((*event)->fsign_right));
 50:   PetscCall(PetscFree((*event)->side));
 51:   PetscCall(PetscFree((*event)->side_prev));
 52:   PetscCall(PetscFree((*event)->justrefined_AB));
 53:   PetscCall(PetscFree((*event)->gamma_AB));
 54:   PetscCall(PetscFree((*event)->direction));
 55:   PetscCall(PetscFree((*event)->terminate));
 56:   PetscCall(PetscFree((*event)->events_zero));
 57:   PetscCall(PetscFree((*event)->vtol));

 59:   for (PetscInt i = 0; i < (*event)->recsize; i++) PetscCall(PetscFree((*event)->recorder.eventidx[i]));
 60:   PetscCall(PetscFree((*event)->recorder.eventidx));
 61:   PetscCall(PetscFree((*event)->recorder.nevents));
 62:   PetscCall(PetscFree((*event)->recorder.stepnum));
 63:   PetscCall(PetscFree((*event)->recorder.time));

 65:   PetscCall(PetscViewerDestroy(&(*event)->monitor));
 66:   PetscCall(PetscFree(*event));
 67:   PetscFunctionReturn(PETSC_SUCCESS);
 68: }

 70: // PetscClangLinter pragma disable: -fdoc-section-header-unknown
 71: /*@
 72:   TSSetPostEventStep - Set the first time step to use after the event

 74:   Logically Collective

 76:   Input Parameters:
 77: + ts  - time integration context
 78: - dt1 - first post event step

 80:   Options Database Key:
 81: . -ts_event_post_event_step <dt1> - first time step after the event

 83:   Level: advanced

 85:   Notes:
 86:   `TSSetPostEventStep()` allows one to set a time step to use immediately following an event.
 87:   Note, if `TSAdapt` is allowed to interfere and reject steps, a large 'dt1' set by `TSSetPostEventStep()` may get truncated,
 88:   resulting in a smaller actual post-event step. See also the warning below regarding the `TSAdapt`.

 90:   The post-event time steps should be selected based on the post-event dynamics.
 91:   If the dynamics are stiff, or a significant jump in the equations or the state vector has taken place at the event,
 92:   conservative (small) steps should be employed. If not, then larger time steps may be appropriate.

 94:   This function accepts either a numerical value for `dt1`, or `PETSC_DECIDE`. The special value `PETSC_DECIDE` signals the event handler to follow
 95:   the originally planned trajectory, and is assumed by default.

 97:   To describe the way `PETSC_DECIDE` affects the post-event steps, consider a trajectory of time points t1 -> t2 -> t3 -> t4.
 98:   Suppose the TS has reached and calculated the solution at point t3, and has planned the next move: t3 -> t4.
 99:   At this moment, an event between t2 and t3 is detected, and after a few iterations it is resolved at point `te`, t2 < te < t3.
100:   After event `te`, two post-event steps can be specified: the first one dt1 (`TSSetPostEventStep()`),
101:   and the second one dt2 (`TSSetPostEventSecondStep()`). Both post-event steps can be either `PETSC_DECIDE`, or a number.
102:   Four different combinations are possible\:

104:   1. dt1 = `PETSC_DECIDE`, dt2 = `PETSC_DECIDE`. Then, after `te` TS goes to t3, and then to t4. This is the all-default behaviour.

106:   2. dt1 = `PETSC_DECIDE`, dt2 = x2 (numerical). Then, after `te` TS goes to t3, and then to t3+x2.

108:   3. dt1 = x1 (numerical), dt2 = x2 (numerical). Then, after `te` TS goes to te+x1, and then to te+x1+x2.

110:   4. dt1 = x1 (numerical), dt2 = `PETSC_DECIDE`. Then, after `te` TS goes to te+x1, and event handler does not interfere to the subsequent steps.

112:   In the special case when `te` == t3 with a good precision, the post-event step te -> t3 is not performed, so behaviour of (1) and (2) becomes\:

114:   1a. After `te` TS goes to t4, and event handler does not interfere to the subsequent steps.

116:   2a. After `te` TS goes to t4, and then to t4+x2.

118:   Warning! When the second post-event step (either PETSC_DECIDE or a numerical value) is managed by the event handler, i.e. in cases 1, 2, 3 and 2a,
119:   `TSAdapt` will never analyse (and never do a reasonable rejection of) the first post-event step. The first post-event step will always be accepted.
120:   In this situation, it is the user's responsibility to make sure the step size is appropriate!
121:   In cases 4 and 1a, however, `TSAdapt` will analyse the first post-event step, and is allowed to reject it.

123:   This function can be called not only in the initial setup, but also inside the `postevent()` callback set with `TSSetEventHandler()`,
124:   affecting the post-event steps for the current event, and the subsequent ones.
125:   Thus, the strategy of the post-event time step definition can be adjusted on the fly.
126:   In case several events are triggered in the given time point, only a single postevent handler is invoked,
127:   and the user is to determine what post-event time step is more appropriate in this situation.

129:   The default value is `PETSC_DECIDE`.

131:   Developer Notes:
132:   Event processing starts after visiting point t3, which means ts->adapt->dt_span_cached has been set to whatever value is required
133:   when planning the step t3 -> t4.

135: .seealso: [](ch_ts), `TS`, `TSEvent`, `TSSetEventHandler()`, `TSSetPostEventSecondStep()`
136: @*/
137: PetscErrorCode TSSetPostEventStep(TS ts, PetscReal dt1)
138: {
139:   PetscFunctionBegin;
140:   ts->event->timestep_postevent = dt1;
141:   PetscFunctionReturn(PETSC_SUCCESS);
142: }

144: // PetscClangLinter pragma disable: -fdoc-section-header-unknown
145: /*@
146:   TSSetPostEventSecondStep - Set the second time step to use after the event

148:   Logically Collective

150:   Input Parameters:
151: + ts  - time integration context
152: - dt2 - second post event step

154:   Options Database Key:
155: . -ts_event_post_event_second_step <dt2> - second time step after the event

157:   Level: advanced

159:   Notes:
160:   `TSSetPostEventSecondStep()` allows one to set the second time step after the event.

162:   The post-event time steps should be selected based on the post-event dynamics.
163:   If the dynamics are stiff, or a significant jump in the equations or the state vector has taken place at the event,
164:   conservative (small) steps should be employed. If not, then larger time steps may be appropriate.

166:   This function accepts either a numerical value for `dt2`, or `PETSC_DECIDE` (default).

168:   To describe the way `PETSC_DECIDE` affects the post-event steps, consider a trajectory of time points t1 -> t2 -> t3 -> t4.
169:   Suppose the TS has reached and calculated the solution at point t3, and has planned the next move: t3 -> t4.
170:   At this moment, an event between t2 and t3 is detected, and after a few iterations it is resolved at point `te`, t2 < te < t3.
171:   After event `te`, two post-event steps can be specified: the first one dt1 (`TSSetPostEventStep()`),
172:   and the second one dt2 (`TSSetPostEventSecondStep()`). Both post-event steps can be either `PETSC_DECIDE`, or a number.
173:   Four different combinations are possible\:

175:   1. dt1 = `PETSC_DECIDE`, dt2 = `PETSC_DECIDE`. Then, after `te` TS goes to t3, and then to t4. This is the all-default behaviour.

177:   2. dt1 = `PETSC_DECIDE`, dt2 = x2 (numerical). Then, after `te` TS goes to t3, and then to t3+x2.

179:   3. dt1 = x1 (numerical), dt2 = x2 (numerical). Then, after `te` TS goes to te+x1, and then to te+x1+x2.

181:   4. dt1 = x1 (numerical), dt2 = `PETSC_DECIDE`. Then, after `te` TS goes to te+x1, and event handler does not interfere to the subsequent steps.

183:   In the special case when `te` == t3 with a good precision, the post-event step te -> t3 is not performed, so behaviour of (1) and (2) becomes\:

185:   1a. After `te` TS goes to t4, and event handler does not interfere to the subsequent steps.

187:   2a. After `te` TS goes to t4, and then to t4+x2.

189:   Warning! When the second post-event step (either PETSC_DECIDE or a numerical value) is managed by the event handler, i.e. in cases 1, 2, 3 and 2a,
190:   `TSAdapt` will never analyse (and never do a reasonable rejection of) the first post-event step. The first post-event step will always be accepted.
191:   In this situation, it is the user's responsibility to make sure the step size is appropriate!
192:   In cases 4 and 1a, however, `TSAdapt` will analyse the first post-event step, and is allowed to reject it.

194:   This function can be called not only in the initial setup, but also inside the `postevent()` callback set with `TSSetEventHandler()`,
195:   affecting the post-event steps for the current event, and the subsequent ones.

197:   The default value is `PETSC_DECIDE`.

199: .seealso: [](ch_ts), `TS`, `TSEvent`, `TSSetEventHandler()`, `TSSetPostEventStep()`
200: @*/
201: PetscErrorCode TSSetPostEventSecondStep(TS ts, PetscReal dt2)
202: {
203:   PetscFunctionBegin;
204:   ts->event->timestep_2nd_postevent = dt2;
205:   PetscFunctionReturn(PETSC_SUCCESS);
206: }

208: /*@
209:   TSSetEventTolerances - Set tolerances for event (indicator function) zero crossings

211:   Logically Collective

213:   Input Parameters:
214: + ts   - time integration context
215: . tol  - tolerance, `PETSC_DECIDE` or `PETSC_DEFAULT` to leave the current value
216: - vtol - array of tolerances or `NULL`, used in preference to `tol` if present

218:   Options Database Key:
219: . -ts_event_tol <tol> - tolerance for event (indicator function) zero crossing

221:   Level: beginner

223:   Notes:
224:   One must call `TSSetEventHandler()` before setting the tolerances.

226:   The size of `vtol` should be equal to the number of events on the given process.

228:   This function can be also called from the `postevent()` callback set with `TSSetEventHandler()`,
229:   to adjust the tolerances on the fly.

231: .seealso: [](ch_ts), `TS`, `TSEvent`, `TSSetEventHandler()`
232: @*/
233: PetscErrorCode TSSetEventTolerances(TS ts, PetscReal tol, PetscReal vtol[])
234: {
235:   TSEvent event;

237:   PetscFunctionBegin;
239:   if (vtol) PetscAssertPointer(vtol, 3);
240:   PetscCheck(ts->event, PetscObjectComm((PetscObject)ts), PETSC_ERR_USER, "Must set the events first by calling TSSetEventHandler()");

242:   event = ts->event;
243:   if (vtol) {
244:     for (PetscInt i = 0; i < event->nevents; i++) event->vtol[i] = vtol[i];
245:   } else {
246:     if (tol != (PetscReal)PETSC_DECIDE && tol != (PetscReal)PETSC_DEFAULT) {
247:       for (PetscInt i = 0; i < event->nevents; i++) event->vtol[i] = tol;
248:     }
249:   }
250:   PetscFunctionReturn(PETSC_SUCCESS);
251: }

253: /*@C
254:   TSSetEventHandler - Sets functions and parameters used for indicating events and handling them

256:   Logically Collective

258:   Input Parameters:
259: + ts        - the `TS` context obtained from `TSCreate()`
260: . nevents   - number of local events (i.e. managed by the given MPI process)
261: . direction - direction of zero crossing to be detected (one for each local event).
262:               `-1` => zero crossing in negative direction,
263:               `+1` => zero crossing in positive direction, `0` => both ways
264: . terminate - flag to indicate whether time stepping should be terminated after
265:               an event is detected (one for each local event)
266: . indicator - callback defininig the user indicator functions whose sign changes (see `direction`) mark presence of the events
267: . postevent - [optional] user post-event callback; it can change the solution, ODE etc at the time of the event
268: - ctx       - [optional] user-defined context for private data for the
269:               `indicator()` and `postevent()` routines (use `NULL` if no
270:               context is desired)

272:   Calling sequence of `indicator`:
273: + ts     - the `TS` context
274: . t      - current time
275: . U      - current solution
276: . fvalue - output array with values of local indicator functions (length == `nevents`) for time t and state-vector U
277: - ctx    - the context passed as the final argument to `TSSetEventHandler()`

279:   Calling sequence of `postevent`:
280: + ts           - the `TS` context
281: . nevents_zero - number of triggered local events (whose indicator function is marked as crossing zero, and direction is appropriate)
282: . events_zero  - indices of the triggered local events
283: . t            - current time
284: . U            - current solution
285: . forwardsolve - flag to indicate whether `TS` is doing a forward solve (`PETSC_TRUE`) or adjoint solve (`PETSC_FALSE`)
286: - ctx          - the context passed as the final argument to `TSSetEventHandler()`

288:   Options Database Keys:
289: + -ts_event_tol <tol>                       - tolerance for zero crossing check of indicator functions
290: . -ts_event_monitor                         - print choices made by event handler
291: . -ts_event_recorder_initial_size <recsize> - initial size of event recorder
292: . -ts_event_post_event_step <dt1>           - first time step after event
293: . -ts_event_post_event_second_step <dt2>    - second time step after event
294: - -ts_event_dt_min <dt>                     - minimum time step considered for TSEvent

296:   Level: intermediate

298:   Notes:
299:   The indicator functions should be defined in the `indicator` callback using the components of solution `U` and/or time `t`.
300:   Note that `U` is `PetscScalar`-valued, and the indicator functions are `PetscReal`-valued. It is the user's responsibility to
301:   properly handle this difference, e.g. by applying `PetscRealPart()` or other appropriate conversion means.

303:   The full set of events is distributed (by the user design) across MPI processes, with each process defining its own local sub-set of events.
304:   However, the `postevent()` callback invocation is performed synchronously on all processes, including
305:   those processes which have not currently triggered any events.

307: .seealso: [](ch_ts), `TSEvent`, `TSCreate()`, `TSSetTimeStep()`, `TSSetConvergedReason()`
308: @*/
309: PetscErrorCode TSSetEventHandler(TS ts, PetscInt nevents, PetscInt direction[], PetscBool terminate[], PetscErrorCode (*indicator)(TS ts, PetscReal t, Vec U, PetscReal fvalue[], void *ctx), PetscErrorCode (*postevent)(TS ts, PetscInt nevents_zero, PetscInt events_zero[], PetscReal t, Vec U, PetscBool forwardsolve, void *ctx), void *ctx)
310: {
311:   TSAdapt   adapt;
312:   PetscReal hmin;
313:   TSEvent   event;
314:   PetscBool flg;
315: #if defined PETSC_USE_REAL_SINGLE
316:   PetscReal tol = 1e-4;
317: #else
318:   PetscReal tol = 1e-6;
319: #endif

321:   PetscFunctionBegin;
323:   if (nevents) {
324:     PetscAssertPointer(direction, 3);
325:     PetscAssertPointer(terminate, 4);
326:   }
327:   PetscCall(PetscNew(&event));
328:   PetscCall(PetscMalloc1(nevents, &event->fvalue_prev));
329:   PetscCall(PetscMalloc1(nevents, &event->fvalue));
330:   PetscCall(PetscMalloc1(nevents, &event->fvalue_right));
331:   PetscCall(PetscMalloc1(nevents, &event->fsign_prev));
332:   PetscCall(PetscMalloc1(nevents, &event->fsign));
333:   PetscCall(PetscMalloc1(nevents, &event->fsign_right));
334:   PetscCall(PetscMalloc1(nevents, &event->side));
335:   PetscCall(PetscMalloc1(nevents, &event->side_prev));
336:   PetscCall(PetscMalloc1(nevents, &event->justrefined_AB));
337:   PetscCall(PetscMalloc1(nevents, &event->gamma_AB));
338:   PetscCall(PetscMalloc1(nevents, &event->direction));
339:   PetscCall(PetscMalloc1(nevents, &event->terminate));
340:   PetscCall(PetscMalloc1(nevents, &event->events_zero));
341:   PetscCall(PetscMalloc1(nevents, &event->vtol));
342:   for (PetscInt i = 0; i < nevents; i++) {
343:     event->direction[i]      = direction[i];
344:     event->terminate[i]      = terminate[i];
345:     event->justrefined_AB[i] = PETSC_FALSE;
346:     event->gamma_AB[i]       = 1;
347:     event->side[i]           = 2;
348:     event->side_prev[i]      = 0;
349:   }
350:   event->iterctr                = 0;
351:   event->processing             = PETSC_FALSE;
352:   event->revisit_right          = PETSC_FALSE;
353:   event->nevents                = nevents;
354:   event->indicator              = indicator;
355:   event->postevent              = postevent;
356:   event->ctx                    = ctx;
357:   event->timestep_postevent     = PETSC_DECIDE;
358:   event->timestep_2nd_postevent = PETSC_DECIDE;
359:   PetscCall(TSGetAdapt(ts, &adapt));
360:   PetscCall(TSAdaptGetStepLimits(adapt, &hmin, NULL));
361:   event->timestep_min = hmin;

363:   event->recsize = 8; /* Initial size of the recorder */
364:   PetscOptionsBegin(((PetscObject)ts)->comm, ((PetscObject)ts)->prefix, "TS Event options", "TS");
365:   {
366:     PetscCall(PetscOptionsReal("-ts_event_tol", "Tolerance for zero crossing check of indicator functions", "TSSetEventTolerances", tol, &tol, NULL));
367:     PetscCall(PetscOptionsName("-ts_event_monitor", "Print choices made by event handler", "", &flg));
368:     PetscCall(PetscOptionsInt("-ts_event_recorder_initial_size", "Initial size of event recorder", "", event->recsize, &event->recsize, NULL));
369:     PetscCall(PetscOptionsDeprecated("-ts_event_post_eventinterval_step", "-ts_event_post_event_second_step", "3.21", NULL));
370:     PetscCall(PetscOptionsReal("-ts_event_post_event_step", "First time step after event", "", event->timestep_postevent, &event->timestep_postevent, NULL));
371:     PetscCall(PetscOptionsReal("-ts_event_post_event_second_step", "Second time step after event", "", event->timestep_2nd_postevent, &event->timestep_2nd_postevent, NULL));
372:     PetscCall(PetscOptionsReal("-ts_event_dt_min", "Minimum time step considered for TSEvent", "", event->timestep_min, &event->timestep_min, NULL));
373:   }
374:   PetscOptionsEnd();

376:   PetscCall(PetscMalloc1(event->recsize, &event->recorder.time));
377:   PetscCall(PetscMalloc1(event->recsize, &event->recorder.stepnum));
378:   PetscCall(PetscMalloc1(event->recsize, &event->recorder.nevents));
379:   PetscCall(PetscMalloc1(event->recsize, &event->recorder.eventidx));
380:   for (PetscInt i = 0; i < event->recsize; i++) PetscCall(PetscMalloc1(event->nevents, &event->recorder.eventidx[i]));
381:   /* Initialize the event recorder */
382:   event->recorder.ctr = 0;

384:   for (PetscInt i = 0; i < event->nevents; i++) event->vtol[i] = tol;
385:   if (flg) PetscCall(PetscViewerASCIIOpen(PETSC_COMM_SELF, "stdout", &event->monitor));

387:   PetscCall(TSEventDestroy(&ts->event));
388:   ts->event        = event;
389:   ts->event->refct = 1;
390:   PetscFunctionReturn(PETSC_SUCCESS);
391: }

393: /*
394:   TSEventRecorderResize - Resizes (2X) the event recorder arrays whenever the recording limit (event->recsize)
395:                           is reached.
396: */
397: static PetscErrorCode TSEventRecorderResize(TSEvent event)
398: {
399:   PetscReal *time;
400:   PetscInt  *stepnum, *nevents;
401:   PetscInt **eventidx;
402:   PetscInt   fact = 2;

404:   PetscFunctionBegin;
405:   /* Create larger arrays */
406:   PetscCall(PetscMalloc1(fact * event->recsize, &time));
407:   PetscCall(PetscMalloc1(fact * event->recsize, &stepnum));
408:   PetscCall(PetscMalloc1(fact * event->recsize, &nevents));
409:   PetscCall(PetscMalloc1(fact * event->recsize, &eventidx));
410:   for (PetscInt i = 0; i < fact * event->recsize; i++) PetscCall(PetscMalloc1(event->nevents, &eventidx[i]));

412:   /* Copy over data */
413:   PetscCall(PetscArraycpy(time, event->recorder.time, event->recsize));
414:   PetscCall(PetscArraycpy(stepnum, event->recorder.stepnum, event->recsize));
415:   PetscCall(PetscArraycpy(nevents, event->recorder.nevents, event->recsize));
416:   for (PetscInt i = 0; i < event->recsize; i++) PetscCall(PetscArraycpy(eventidx[i], event->recorder.eventidx[i], event->recorder.nevents[i]));

418:   /* Destroy old arrays */
419:   for (PetscInt i = 0; i < event->recsize; i++) PetscCall(PetscFree(event->recorder.eventidx[i]));
420:   PetscCall(PetscFree(event->recorder.eventidx));
421:   PetscCall(PetscFree(event->recorder.nevents));
422:   PetscCall(PetscFree(event->recorder.stepnum));
423:   PetscCall(PetscFree(event->recorder.time));

425:   /* Set pointers */
426:   event->recorder.time     = time;
427:   event->recorder.stepnum  = stepnum;
428:   event->recorder.nevents  = nevents;
429:   event->recorder.eventidx = eventidx;

431:   /* Update the size */
432:   event->recsize *= fact;
433:   PetscFunctionReturn(PETSC_SUCCESS);
434: }

436: /*
437:   Helper routine to handle user postevents and recording
438: */
439: static PetscErrorCode TSPostEvent(TS ts, PetscReal t, Vec U)
440: {
441:   TSEvent   event        = ts->event;
442:   PetscBool restart      = PETSC_FALSE;
443:   PetscBool terminate    = PETSC_FALSE;
444:   PetscBool statechanged = PETSC_FALSE;
445:   PetscInt  ctr, stepnum;
446:   PetscBool inflag[3], outflag[3];
447:   PetscBool forwardsolve = PETSC_TRUE; // Flag indicating that TS is doing a forward solve

449:   PetscFunctionBegin;
450:   if (event->postevent) {
451:     PetscObjectState state_prev, state_post;
452:     PetscCall(PetscObjectStateGet((PetscObject)U, &state_prev));
453:     PetscCallBack("TSEvent post-event processing", (*event->postevent)(ts, event->nevents_zero, event->events_zero, t, U, forwardsolve, event->ctx)); // TODO update 'restart' here?
454:     PetscCall(PetscObjectStateGet((PetscObject)U, &state_post));
455:     if (state_prev != state_post) {
456:       restart      = PETSC_TRUE;
457:       statechanged = PETSC_TRUE;
458:     }
459:   }

461:   // Handle termination events and step restart
462:   for (PetscInt i = 0; i < event->nevents_zero; i++)
463:     if (event->terminate[event->events_zero[i]]) terminate = PETSC_TRUE;
464:   inflag[0] = restart;
465:   inflag[1] = terminate;
466:   inflag[2] = statechanged;
467:   PetscCall(MPIU_Allreduce(inflag, outflag, 3, MPIU_BOOL, MPI_LOR, ((PetscObject)ts)->comm));
468:   restart      = outflag[0];
469:   terminate    = outflag[1];
470:   statechanged = outflag[2];
471:   if (restart) PetscCall(TSRestartStep(ts));
472:   if (terminate) PetscCall(TSSetConvergedReason(ts, TS_CONVERGED_EVENT));

474:   /*
475:     Recalculate the indicator functions and signs if the state has been changed by the user postevent callback.
476:     Note! If the state HAS NOT changed, the existing event->fsign (equal to zero) is kept, which:
477:     - might have been defined using the previous (now-possibly-overridden) event->vtol,
478:     - might have been set to zero on reaching a small time step rather than using the vtol criterion.
479:     This will enforce keeping event->fsign = 0 where the zero-crossings were actually marked,
480:     resulting in a more consistent behaviour of fsign's.
481:   */
482:   if (statechanged) {
483:     if (event->monitor) PetscCall(PetscPrintf(((PetscObject)ts)->comm, "TSEvent: at time %g the vector state has been changed by PostEvent, recalculating fvalues and signs\n", (double)t));
484:     PetscCall(VecLockReadPush(U));
485:     PetscCallBack("TSEvent indicator", (*event->indicator)(ts, t, U, event->fvalue, event->ctx));
486:     PetscCall(VecLockReadPop(U));
487:     TSEventCalcSigns(event->nevents, event->fvalue, event->vtol, event->fsign); // note, event->vtol might have been changed by the postevent()
488:   }

490:   // Record the event in the event recorder
491:   PetscCall(TSGetStepNumber(ts, &stepnum));
492:   ctr = event->recorder.ctr;
493:   if (ctr == event->recsize) PetscCall(TSEventRecorderResize(event));
494:   event->recorder.time[ctr]    = t;
495:   event->recorder.stepnum[ctr] = stepnum;
496:   event->recorder.nevents[ctr] = event->nevents_zero;
497:   for (PetscInt i = 0; i < event->nevents_zero; i++) event->recorder.eventidx[ctr][i] = event->events_zero[i];
498:   event->recorder.ctr++;
499:   PetscFunctionReturn(PETSC_SUCCESS);
500: }

502: // PetscClangLinter pragma disable: -fdoc-sowing-chars
503: /*
504:   (modified) Anderson-Bjorck variant of regula falsi method, refines [tleft, t] or [t, tright] based on 'side' (-1 or +1).
505:   The scaling parameter is defined based on the 'justrefined' flag, the history of repeats of 'side', and the threshold.
506:   To escape certain failure modes, the algorithm may drift towards the bisection rule.
507:   The value pointed to by 'side_prev' gets updated.
508:   This function returns the new time step.

510:   The underlying pure Anderson-Bjorck algorithm was taken as described in
511:   J.M. Fernandez-Diaz, C.O. Menendez-Perez "A common framework for modified Regula Falsi methods and new methods of this kind", 2023.
512:   The modifications subsequently introduced have little effect on the behaviour for simple cases requiring only a few iterations
513:   (some minor convergence slowdown may take place though), but the effect becomes more pronounced for tough cases requiring many iterations.
514:   For the latter situation the speed-up may be order(s) of magnitude compared to the classical Anderson-Bjorck.
515:   The modifications (the threshold trick, and the drift towards bisection) were tested and tweaked
516:   based on a number of test functions from the mentioned paper.
517: */
518: static inline PetscReal RefineAndersonBjorck(PetscReal tleft, PetscReal t, PetscReal tright, PetscReal fleft, PetscReal f, PetscReal fright, PetscInt side, PetscInt *side_prev, PetscBool justrefined, PetscReal *gamma)
519: {
520:   PetscReal      new_dt, scal = 1.0, scalB = 1.0, threshold = 0.0, power;
521:   PetscInt       reps     = 0;
522:   const PetscInt REPS_CAP = 8; // an upper bound to be imposed on 'reps' (set to 8, somewhat arbitrary number, found after some tweaking)

524:   // Preparations
525:   if (justrefined) {
526:     if (*side_prev * side > 0) *side_prev += side;     // the side keeps repeating -> increment the side counter (-ve or +ve)
527:     else *side_prev = side;                            // reset the counter
528:     reps      = PetscMin(*side_prev * side, REPS_CAP); // the length of the recent side-repeat series, including the current 'side'
529:     threshold = PetscPowReal(0.5, reps) * 0.1;         // ad-hoc strategy for threshold calculation (involved some tweaking)
530:   } else *side_prev = side;                            // initial reset of the counter

532:   // Time step calculation
533:   if (side == -1) {
534:     if (justrefined && fright != 0.0 && fleft != 0.0) {
535:       scal  = (fright - f) / fright;
536:       scalB = -f / fleft;
537:     }
538:   } else { // must be side == +1
539:     if (justrefined && fleft != 0.0 && fright != 0.0) {
540:       scal  = (fleft - f) / fleft;
541:       scalB = -f / fright;
542:     }
543:   }

545:   if (scal < threshold) scal = 0.5;
546:   if (reps > 1) *gamma *= scal; // side did not switch since the last time, accumulate gamma
547:   else *gamma = 1.0;            // side switched -> reset gamma
548:   power = PetscMax(0.0, (reps - 2.0) / (REPS_CAP - 2.0));
549:   scal  = PetscPowReal(scalB / *gamma, power) * (*gamma); // mix the Anderson-Bjorck scaling and Bisection scaling

551:   if (side == -1) new_dt = scal * fleft / (scal * fleft - f) * (t - tleft);
552:   else new_dt = f / (f - scal * fright) * (tright - t);
553:   /*
554:     In tough cases (e.g. a polynomial of high order), there is a failure mode for the standard Anderson-Bjorck,
555:     when the new proposed point jumps from one end-point of the bracket to the other, however the bracket
556:     is contracting very slowly. A larger threshold for 'scal' prevents entering this mode.
557:     On the other hand, if the iteration gets stuck near one end-point of the bracket, and the 'side' does not switch for a while,
558:     the 'scal' drifts towards the bisection approach (via scalB), ensuring stable convergence.
559:   */
560:   return new_dt;
561: }

563: /*
564:   Checks if the current point (t) is the zero-crossing location, based on the indicator function signs and direction[]:
565:   - using the dt_min criterion,
566:   - using the vtol criterion.
567:   The situation (fsign_prev, fsign) = (0, 0) is treated as staying in the near-zero-zone of the previous zero-crossing,
568:   and is not marked as a new zero-crossing.
569:   This function may update event->side[].
570: */
571: static PetscErrorCode TSEventTestZero(TS ts, PetscReal t)
572: {
573:   TSEvent event = ts->event;

575:   PetscFunctionBegin;
576:   for (PetscInt i = 0; i < event->nevents; i++) {
577:     const PetscBool bracket_is_left = (event->fsign_prev[i] * event->fsign[i] < 0 && event->fsign[i] * event->direction[i] >= 0) ? PETSC_TRUE : PETSC_FALSE;

579:     if (bracket_is_left && ((t - event->ptime_prev <= event->timestep_min) || event->revisit_right)) event->side[i] = 0;          // mark zero-crossing from dt_min; 'bracket_is_left' accounts for direction
580:     if (event->fsign[i] == 0 && event->fsign_prev[i] != 0 && event->fsign_prev[i] * event->direction[i] <= 0) event->side[i] = 0; // mark zero-crossing from vtol
581:   }
582:   PetscFunctionReturn(PETSC_SUCCESS);
583: }

585: /*
586:   Checks if [fleft, f] or [f, fright] are 'brackets', i.e. intervals with the sign change, satisfying the 'direction'.
587:   The right interval is only checked if iterctr > 0 (i.e. Anderson-Bjorck refinement has started).
588:   The intervals like [0, x] and [x, 0] are not counted as brackets, i.e. intervals with the sign change.
589:   The function returns the 'side' value: -1 (left, or both are brackets), +1 (only right one), +2 (neither).
590: */
591: static inline PetscInt TSEventTestBracket(PetscInt fsign_left, PetscInt fsign, PetscInt fsign_right, PetscInt direction, PetscInt iterctr)
592: {
593:   PetscInt side = 2;
594:   if (fsign_left * fsign < 0 && fsign * direction >= 0) side = -1;
595:   if (side != -1 && iterctr > 0 && fsign * fsign_right < 0 && fsign_right * direction >= 0) side = 1;
596:   return side;
597: }

599: /*
600:   Caps the time steps, accounting for time span points.
601:   It uses 'event->timestep_cache' as a time step to calculate the tolerance for tspan points detection. This
602:   is done since the event resolution may result in significant time step refinement, and we don't use these small steps for tolerances.
603:   To enhance the consistency of tspan points detection, tolerance 'tspan->worktol' is reused later in the TSSolve iteration.
604:   If a user-defined step is cut by this function, the input uncut step is saved to adapt->dt_span_cached.
605:   Flag 'user_dt' indicates if the step was defined by user.
606: */
607: static inline PetscReal TSEvent_dt_cap(TS ts, PetscReal t, PetscReal dt, PetscBool user_dt)
608: {
609:   PetscReal res = dt;
610:   if (ts->exact_final_time == TS_EXACTFINALTIME_MATCHSTEP) {
611:     PetscReal maxdt    = ts->max_time - t; // this may be overridden by tspan
612:     PetscBool cut_made = PETSC_FALSE;
613:     PetscReal eps      = 10 * PETSC_MACHINE_EPSILON;
614:     if (ts->tspan) {
615:       PetscInt   ctr = ts->tspan->spanctr;
616:       PetscInt   Ns  = ts->tspan->num_span_times;
617:       PetscReal *st  = ts->tspan->span_times;

619:       if (ts->tspan->worktol == 0) ts->tspan->worktol = ts->tspan->reltol * ts->event->timestep_cache + ts->tspan->abstol; // in case TSAdaptChoose() has not defined it
620:       if (ctr < Ns && PetscIsCloseAtTol(t, st[ctr], ts->tspan->worktol, 0)) {                                              // just hit a time span point
621:         if (ctr + 1 < Ns) maxdt = st[ctr + 1] - t;                                                                         // ok to use the next time span point
622:         else maxdt = ts->max_time - t;                                                                                     // can't use the next time span point: they have finished
623:       } else if (ctr < Ns) maxdt = st[ctr] - t;                                                                            // haven't hit a time span point, use the nearest one
624:     }
625:     maxdt = PetscMin(maxdt, ts->max_time - t);
626:     PetscCheck((maxdt > eps) || (PetscAbsReal(maxdt) <= eps && PetscIsCloseAtTol(t, ts->max_time, eps, 0)), PetscObjectComm((PetscObject)ts), PETSC_ERR_PLIB, "Unexpected state: bad maxdt in TSEvent_dt_cap()");

628:     if (PetscIsCloseAtTol(dt, maxdt, eps, 0)) res = maxdt; // no cut
629:     else {
630:       if (dt > maxdt) {
631:         res      = maxdt; // yes cut
632:         cut_made = PETSC_TRUE;
633:       } else res = dt; // no cut
634:     }
635:     if (ts->adapt && user_dt) { // only update dt_span_cached for the user-defined step
636:       if (cut_made) ts->adapt->dt_span_cached = dt;
637:       else ts->adapt->dt_span_cached = 0;
638:     }
639:   }
640:   return res;
641: }

643: /*
644:   Updates the left-end values
645: */
646: static inline void TSEvent_update_left(TSEvent event, PetscReal t)
647: {
648:   for (PetscInt i = 0; i < event->nevents; i++) {
649:     event->fvalue_prev[i] = event->fvalue[i];
650:     event->fsign_prev[i]  = event->fsign[i];
651:   }
652:   event->ptime_prev = t;
653: }

655: /*
656:   Updates the right-end values
657: */
658: static inline void TSEvent_update_right(TSEvent event, PetscReal t)
659: {
660:   for (PetscInt i = 0; i < event->nevents; i++) {
661:     event->fvalue_right[i] = event->fvalue[i];
662:     event->fsign_right[i]  = event->fsign[i];
663:   }
664:   event->ptime_right = t;
665: }

667: /*
668:   Updates the current values from the right-end values
669: */
670: static inline PetscReal TSEvent_update_from_right(TSEvent event)
671: {
672:   for (PetscInt i = 0; i < event->nevents; i++) {
673:     event->fvalue[i] = event->fvalue_right[i];
674:     event->fsign[i]  = event->fsign_right[i];
675:   }
676:   return event->ptime_right;
677: }

679: static inline PetscBool Not_PETSC_DECIDE(PetscReal dt)
680: {
681:   return dt == PETSC_DECIDE ? PETSC_FALSE : PETSC_TRUE;
682: }

684: // PetscClangLinter pragma disable: -fdoc-section-spacing
685: // PetscClangLinter pragma disable: -fdoc-section-header-unknown
686: // PetscClangLinter pragma disable: -fdoc-section-header-spelling
687: // PetscClangLinter pragma disable: -fdoc-section-header-missing
688: // PetscClangLinter pragma disable: -fdoc-section-header-fishy-header
689: // PetscClangLinter pragma disable: -fdoc-param-list-func-parameter-documentation
690: // PetscClangLinter pragma disable: -fdoc-synopsis-missing-description
691: // PetscClangLinter pragma disable: -fdoc-sowing-chars
692: /*
693:   TSEventHandler - the main function to perform a single iteration of event detection.

695:   Developer notes:
696:   A) The 'event->iterctr > 0' is used as an indicator that Anderson-Bjorck refinement has started.
697:   B) If event->iterctr == 0, then justrefined_AB[i] is always false.
698:   C) The right-end quantities: ptime_right, fvalue_right[i] and fsign_right[i] are only guaranteed to be valid
699:   for event->iterctr > 0.
700:   D) If event->iterctr > 0, then event->processing is PETSC_TRUE; the opposite may not hold.
701:   E) When event->processing == PETSC_TRUE and event->iterctr == 0, the event handler iterations are complete, but
702:   the event handler continues managing the 1st and 2nd post-event steps. In this case the 1st post-event step
703:   proposed by the event handler is not checked by TSAdapt, and is always accepted (beware!).
704:   However, if the 2nd post-event step is not managed by the event handler (e.g. 1st = numerical, 2nd = PETSC_DECIDE),
705:   condition "E" does not hold, and TSAdapt may reject/adjust the 1st post-event step.
706:   F) event->side[i] may take values: 0 <=> point t is a zero-crossing for indicator function i (via vtol/dt_min criterion);
707:   -1/+1 <=> detected a bracket to the left/right of t for indicator function i; +2 <=> no brackets/zero-crossings.
708:   G) The signs event->fsign[i] (with values 0/-1/+1) are calculated for each new point. Zero sign is set if the function value is
709:   smaller than the tolerance. Besides, zero sign is enforced after marking a zero-crossing due to small bracket size criterion.

711:   The intervals with the indicator function sign change (i.e. containing the potential zero-crossings) are called 'brackets'.
712:   To find a zero-crossing, the algorithm first locates a bracket, and then sequentially subdivides it, generating a sequence
713:   of brackets whose length tends to zero. The bracket subdivision involves the (modified) Anderson-Bjorck method.

715:   Apart from the comments scattered throughout the code to clarify different lines and blocks,
716:   a few tricky aspects of the algorithm (and the underlying reasoning) are discussed in detail below:

718:   =Sign tracking=
719:   When a zero-crossing is found, the sign variable (event->fsign[i]) is set to zero for the current point t.
720:   This happens both for zero-crossings triggered via the vtol criterion, and those triggered via the dt_min
721:   criterion. After the event, as the TS steps forward, the current sign values are handed over to event->fsign_prev[i].
722:   The recalculation of signs is avoided if possible: e.g. if a 'vtol' criterion resulted in a zero-crossing at point t,
723:   but the subsequent call to postevent() handler decreased 'vtol', making the indicator function no longer "close to zero"
724:   at point t, the fsign[i] will still consistently keep the zero value. This allows avoiding the erroneous duplication
725:   of events:

727:   E.g. consider a bracket [t0, t2], where f0 < 0, f2 > 0, which resulted in a zero-crossing t1 with f1 < 0, abs(f1) < vtol.
728:   Suppose the postevent() handler changes vtol to vtol*, such that abs(f1) > vtol*. The TS makes a step t1 -> t3, where
729:   again f1 < 0, f3 > 0, and the event handler will find a new event near t1, which is actually a duplication of the
730:   original event at t1. The duplications are avoided by NOT counting the sign progressions 0 -> +1, or 0 -> -1
731:   as brackets. Tracking (instead of recalculating) the sign values makes this procedure work more consistently.

733:   The sign values are however recalculated if the postevent() callback has changed the current solution vector U
734:   (such a change resets everything).
735:   The sign value is also set to zero if the dt_min criterion has triggered the event. This allows the algorithm to
736:   work consistently, irrespective of the type of criterion involved (vtol/dt_min).

738:   =Event from min bracket=
739:   When the event handler ends up with a bracket [t0, t1] with size <= dt_min, a zero crossing is reported at t1,
740:   and never at t0. If such a bracket is discovered when TS is staying at t0, one more step forward (to t1) is necessary
741:   to mark the found event. This is the situation of revisiting t1, which is described below (see Revisiting).

743:   Why t0 is not reported as event location? Suppose it is, and let f0 < 0, f1 > 0. Also suppose that the
744:   postevent() handler has slightly changed the solution U, so the sign at t0 is recalculated: it equals -1. As the TS steps
745:   further: t0 -> t2, with sign0 == -1, and sign2 == +1, the event handler will locate the bracket [t0, t2], eventually
746:   resolving a new event near t1, i.e. finding a duplicate event.
747:   This situation is avoided by reporting the event at t1 in the first place.

749:   =Revisiting=
750:   When handling the situation with small bracket size, the TS solver may happen to visit the same point twice,
751:   but with different results.

753:   E.g. originally it discovered a bracket with sign change [t0, t10], and started resolving the zero-crossing,
754:   visiting the points t1,...,t9 : t0 < t1 < ... < t9 < t10. Suppose that at t9 the algorithm discovers
755:   that [t9, t10] is a bracket with the sign change it was looking for, and that |t10 - t9| is too small.
756:   So point t10 should be revisited and marked as the zero crossing (by the minimum bracket size criterion).
757:   On re-visiting t10, via the refined sequence of steps t0,...,t10, the TS solver may arrive at a solution U*
758:   different from the solution U it found at t10 originally. Hence, the indicator functions at t10 may become different,
759:   and the condition of the sign change, which existed originally, may disappear, breaking the logic of the algorithm.

761:   To handle such (-=unlikely=-, but possible) situations, two strategies can be considered:
762:   1) [not used here] Allow the brackets with sign change to disappear during iterations. The algorithm should be able
763:   to cleanly exit the iteration and leave all the objects/variables/caches involved in a valid state.
764:   2) [ADOPTED HERE!] On revisiting t10, the event handler reuses the indicator functions previously calculated for the
765:   original solution U. This U may be less precise than U*, but this trick does not allow the algorithm logic to break down.
766:   HOWEVER, the original U is not stored anywhere, it is essentially lost since the TS performed the rollback from it.
767:   On revisiting t10, the updated solution U* will inevitably be found and used everywhere EXCEPT the current
768:   indicator functions calculation, e.g. U* will be used in the postevent() handler call. Since t10 is the event location,
769:   the appropriate indicator-function-signs will be enforced to be 0 (regardless if the solution was U or U*).
770:   If the solution is then changed by the postevent(), the indicator-function-signs will be recalculated.

772:   Whether the algorithm is revisiting a point in the current TSEventHandler() call is flagged by 'event->revisit_right'.
773: */
774: PetscErrorCode TSEventHandler(TS ts)
775: {
776:   TSEvent   event;
777:   PetscReal t, dt_next = 0.0;
778:   Vec       U;
779:   PetscInt  minsidein = 2, minsideout = 2; // minsideout is sync on all ranks
780:   PetscBool finished = PETSC_FALSE;        // should stay sync on all ranks
781:   PetscBool revisit_right_cache;           // [sync] flag for inner consistency checks

783:   PetscFunctionBegin;

786:   if (!ts->event) PetscFunctionReturn(PETSC_SUCCESS);
787:   event               = ts->event;
788:   event->nevents_zero = 0;
789:   revisit_right_cache = event->revisit_right;
790:   for (PetscInt i = 0; i < event->nevents; i++) event->side[i] = 2; // side's are reset on each new iteration
791:   if (event->iterctr == 0)
792:     for (PetscInt i = 0; i < event->nevents; i++) event->justrefined_AB[i] = PETSC_FALSE;

794:   PetscCall(TSGetTime(ts, &t));
795:   if (!event->processing) { // update the caches
796:     PetscReal dt;
797:     PetscCall(TSGetTimeStep(ts, &dt));
798:     event->ptime_cache    = t;
799:     event->timestep_cache = dt; // the next TS move is planned to be: t -> t+dt
800:   }
801:   if (event->processing && event->iterctr == 0 && Not_PETSC_DECIDE(event->timestep_2nd_postevent)) { // update the caches while processing the post-event steps
802:     event->ptime_cache    = t;
803:     event->timestep_cache = event->timestep_2nd_postevent;
804:   }

806:   PetscCall(TSGetSolution(ts, &U)); // if revisiting, this will be the updated U* (see discussion on "Revisiting" in the Developer notes above)
807:   if (event->revisit_right) {
808:     PetscReal tr = TSEvent_update_from_right(event);
809:     PetscCheck(PetscAbsReal(tr - t) < PETSC_SMALL, PetscObjectComm((PetscObject)ts), PETSC_ERR_PLIB, "Inconsistent time value when performing 'revisiting' in TSEventHandler()");
810:   } else {
811:     PetscCall(VecLockReadPush(U));
812:     PetscCallBack("TSEvent indicator", (*event->indicator)(ts, t, U, event->fvalue, event->ctx)); // fill fvalue's at point 't'
813:     PetscCall(VecLockReadPop(U));
814:     TSEventCalcSigns(event->nevents, event->fvalue, event->vtol, event->fsign); // fill fvalue signs
815:   }
816:   PetscCall(TSEventTestZero(ts, t)); // check if the current point 't' is the event location; event->side[] may get updated

818:   for (PetscInt i = 0; i < event->nevents; i++) { // check for brackets on the left/right of 't'
819:     if (event->side[i] != 0) event->side[i] = TSEventTestBracket(event->fsign_prev[i], event->fsign[i], event->fsign_right[i], event->direction[i], event->iterctr);
820:     minsidein = PetscMin(minsidein, event->side[i]);
821:   }
822:   PetscCall(MPIU_Allreduce(&minsidein, &minsideout, 1, MPIU_INT, MPI_MIN, PetscObjectComm((PetscObject)ts)));
823:   /*
824:     minsideout (sync on all ranks) indicates the minimum of the following states:
825:     -1 : [ptime_prev, t] is a bracket for some indicator-function-i
826:     +1 : [t, ptime_right] is a bracket for some indicator-function-i
827:      0 : t is a zero-crossing for some indicator-function-i
828:      2 : none of the above
829:   */
830:   PetscCheck(!event->revisit_right || minsideout == 0, PetscObjectComm((PetscObject)ts), PETSC_ERR_PLIB, "minsideout != 0 when performing 'revisiting' in TSEventHandler()");

832:   if (minsideout == -1 || minsideout == +1) {                                                           // this if-branch will refine the left/right bracket
833:     const PetscReal bracket_size = (minsideout == -1) ? t - event->ptime_prev : event->ptime_right - t; // sync on all ranks

835:     if (minsideout == +1 && bracket_size <= event->timestep_min) { // check if the bracket (right) is small
836:       // [--------------------|-]
837:       dt_next              = bracket_size; // need one more step to get to event->ptime_right
838:       event->revisit_right = PETSC_TRUE;
839:       TSEvent_update_left(event, t);
840:       if (event->monitor)
841:         PetscCall(PetscViewerASCIIPrintf(event->monitor, "[%d] TSEvent: iter %" PetscInt_FMT " - reached too small bracket [%g - %g], next stepping to its right end %g (revisiting)\n", PetscGlobalRank, event->iterctr, (double)event->ptime_prev,
842:                                          (double)event->ptime_right, (double)(event->ptime_prev + dt_next)));
843:     } else { // the bracket is not very small -> refine it
844:       // [--------|-------------]
845:       if (bracket_size <= 2 * event->timestep_min) dt_next = bracket_size / 2; // the bracket is almost small -> bisect it
846:       else {                                                                   // the bracket is not small -> use Anderson-Bjorck
847:         PetscReal dti_min = PETSC_MAX_REAL;
848:         for (PetscInt i = 0; i < event->nevents; i++) {
849:           if (event->side[i] == minsideout) { // only refine the appropriate brackets
850:             PetscReal dti = RefineAndersonBjorck(event->ptime_prev, t, event->ptime_right, event->fvalue_prev[i], event->fvalue[i], event->fvalue_right[i], event->side[i], &event->side_prev[i], event->justrefined_AB[i], &event->gamma_AB[i]);
851:             dti_min       = PetscMin(dti_min, dti);
852:           }
853:         }
854:         PetscCall(MPIU_Allreduce(&dti_min, &dt_next, 1, MPIU_REAL, MPIU_MIN, PetscObjectComm((PetscObject)ts)));
855:         if (dt_next < event->timestep_min) dt_next = event->timestep_min;
856:         if (bracket_size - dt_next < event->timestep_min) dt_next = bracket_size - event->timestep_min;
857:       }

859:       if (minsideout == -1) { // minsideout == -1, update the right-end values, retain the left-end values
860:         TSEvent_update_right(event, t);
861:         PetscCall(TSRollBack(ts));
862:         PetscCall(TSSetConvergedReason(ts, TS_CONVERGED_ITERATING)); // e.g. to override TS_CONVERGED_TIME on reaching ts->max_time
863:       } else TSEvent_update_left(event, t);                          // minsideout == +1, update the left-end values, retain the right-end values

865:       for (PetscInt i = 0; i < event->nevents; i++) { // update the "Anderson-Bjorck" flags
866:         if (event->side[i] == minsideout) {
867:           event->justrefined_AB[i] = PETSC_TRUE; // only for these i's Anderson-Bjorck was invoked
868:           if (event->monitor)
869:             PetscCall(PetscViewerASCIIPrintf(event->monitor, "[%d] TSEvent: iter %" PetscInt_FMT " - Event %" PetscInt_FMT " refining the bracket with sign change [%g - %g], next stepping to %g\n", PetscGlobalRank, event->iterctr, i, (double)event->ptime_prev,
870:                                              (double)event->ptime_right, (double)(event->ptime_prev + dt_next)));
871:         } else event->justrefined_AB[i] = PETSC_FALSE; // for these i's Anderson-Bjorck was not invoked
872:       }
873:     }
874:     event->iterctr++;
875:     event->processing = PETSC_TRUE;
876:   } else if (minsideout == 0) { // found the appropriate zero-crossing (and no brackets to the left), finishing!
877:     // [--------0-------------]
878:     finished             = PETSC_TRUE;
879:     event->revisit_right = PETSC_FALSE;
880:     for (PetscInt i = 0; i < event->nevents; i++)
881:       if (event->side[i] == minsideout) {
882:         event->events_zero[event->nevents_zero++] = i;
883:         if (event->fsign[i] == 0) { // vtol was engaged
884:           if (event->monitor)
885:             PetscCall(PetscViewerASCIIPrintf(event->monitor, "[%d] TSEvent: iter %" PetscInt_FMT " - Event %" PetscInt_FMT " zero crossing located at time %g (tol=%g)\n", PetscGlobalRank, event->iterctr, i, (double)t, (double)event->vtol[i]));
886:         } else {               // dt_min was engaged
887:           event->fsign[i] = 0; // sign = 0 is enforced further
888:           if (event->monitor)
889:             PetscCall(PetscViewerASCIIPrintf(event->monitor, "[%d] TSEvent: iter %" PetscInt_FMT " - Event %" PetscInt_FMT " accepting time %g as event location, due to reaching too small bracket [%g - %g]\n", PetscGlobalRank, event->iterctr, i, (double)t,
890:                                              (double)event->ptime_prev, (double)t));
891:         }
892:       }
893:     event->iterctr++;
894:     event->processing = PETSC_TRUE;
895:   } else { // minsideout == 2: no brackets, no zero-crossings
896:     // [----------------------]
897:     PetscCheck(event->iterctr == 0, PetscObjectComm((PetscObject)ts), PETSC_ERR_PLIB, "Unexpected state (event->iterctr != 0) in TSEventHandler()");
898:     if (event->processing) {
899:       PetscReal dt2;
900:       if (event->timestep_2nd_postevent == PETSC_DECIDE) dt2 = event->timestep_cache;                            // (1)
901:       else dt2 = event->timestep_2nd_postevent;                                                                  // (2), (2a), (3)
902:       PetscCall(TSSetTimeStep(ts, TSEvent_dt_cap(ts, t, dt2, Not_PETSC_DECIDE(event->timestep_2nd_postevent)))); // set the second post-event step
903:     }
904:     event->processing = PETSC_FALSE;
905:   }

907:   // if 'revisit_right' was flagged before the current iteration started, the iteration is expected to finish
908:   PetscCheck(!revisit_right_cache || finished, PetscObjectComm((PetscObject)ts), PETSC_ERR_PLIB, "Unexpected state of 'revisit_right_cache' in TSEventHandler()");

910:   if (finished) { // finished handling the current event
911:     PetscCall(TSPostEvent(ts, t, U));

913:     PetscReal dt1;
914:     if (event->timestep_postevent == PETSC_DECIDE) { // (1), (2)
915:       dt1               = event->ptime_cache - t;
916:       event->processing = PETSC_TRUE;
917:       if (PetscAbsReal(dt1) < PETSC_SMALL) { // (1a), (2a): the cached post-event point == event point
918:         dt1               = event->timestep_cache;
919:         event->processing = Not_PETSC_DECIDE(event->timestep_2nd_postevent);
920:       }
921:     } else {                                         // (3), (4)
922:       dt1               = event->timestep_postevent; // 1st post-event dt = user-provided value
923:       event->processing = Not_PETSC_DECIDE(event->timestep_2nd_postevent);
924:     }

926:     PetscCall(TSSetTimeStep(ts, TSEvent_dt_cap(ts, t, dt1, Not_PETSC_DECIDE(event->timestep_postevent)))); // set the first post-event step
927:     event->iterctr = 0;
928:   } // if-finished

930:   if (event->iterctr == 0) TSEvent_update_left(event, t); // not found an event, or finished the event
931:   else {
932:     PetscCall(TSGetTime(ts, &t));                                              // update 't' to account for potential rollback
933:     PetscCall(TSSetTimeStep(ts, TSEvent_dt_cap(ts, t, dt_next, PETSC_FALSE))); // continue resolving the event
934:   }
935:   PetscFunctionReturn(PETSC_SUCCESS);
936: }

938: PetscErrorCode TSAdjointEventHandler(TS ts)
939: {
940:   TSEvent   event;
941:   PetscReal t;
942:   Vec       U;
943:   PetscInt  ctr;
944:   PetscBool forwardsolve = PETSC_FALSE; // Flag indicating that TS is doing an adjoint solve

946:   PetscFunctionBegin;
948:   if (!ts->event) PetscFunctionReturn(PETSC_SUCCESS);
949:   event = ts->event;

951:   PetscCall(TSGetTime(ts, &t));
952:   PetscCall(TSGetSolution(ts, &U));

954:   ctr = event->recorder.ctr - 1;
955:   if (ctr >= 0 && PetscAbsReal(t - event->recorder.time[ctr]) < PETSC_SMALL) {
956:     // Call the user post-event function
957:     if (event->postevent) {
958:       PetscCallBack("TSEvent post-event processing", (*event->postevent)(ts, event->recorder.nevents[ctr], event->recorder.eventidx[ctr], t, U, forwardsolve, event->ctx));
959:       event->recorder.ctr--;
960:     }
961:   }
962:   PetscCall(PetscBarrier((PetscObject)ts));
963:   PetscFunctionReturn(PETSC_SUCCESS);
964: }

966: /*@
967:   TSGetNumEvents - Get the number of events defined on the given MPI process

969:   Logically Collective

971:   Input Parameter:
972: . ts - the `TS` context

974:   Output Parameter:
975: . nevents - the number of local events on each MPI process

977:   Level: intermediate

979: .seealso: [](ch_ts), `TSEvent`, `TSSetEventHandler()`
980: @*/
981: PetscErrorCode TSGetNumEvents(TS ts, PetscInt *nevents)
982: {
983:   PetscFunctionBegin;
984:   *nevents = ts->event->nevents;
985:   PetscFunctionReturn(PETSC_SUCCESS);
986: }