xref: /petsc/src/ts/event/tsevent.c (revision ca4445c7a2f5ca546b532f08b853c371604af17c)
1 #include <petsc/private/tsimpl.h> /*I  "petscts.h" I*/
2 
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 }
14 
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);
23   PetscValidHeaderSpecific(ts, TS_CLASSID, 2);
24   PetscValidHeaderSpecific(U, VEC_CLASSID, 4);
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 }
33 
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   }
43 
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));
58 
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));
64 
65   PetscCall(PetscViewerDestroy(&(*event)->monitor));
66   PetscCall(PetscFree(*event));
67   PetscFunctionReturn(PETSC_SUCCESS);
68 }
69 
70 /*@
71   TSSetPostEventStep - Set the time step to use immediately following the event
72 
73   Logically Collective
74 
75   Input Parameters:
76 + ts - time integration context
77 - dt - post event step
78 
79   Options Database Key:
80 . -ts_event_post_event_step <dt> - time step after the event; zero value - to keep using previous time steps
81 
82   Level: advanced
83 
84   Notes:
85   `TSSetPostEventStep()` allows one to set a time step that is used immediately following an event.
86   If a positive real number is specified, it will be applied as is.
87   However, if `TSAdapt` is allowed to interfere, a large 'dt' may get truncated, resulting in a smaller actual post-event step.
88 
89   The post-event time step should be selected based on the post-event dynamics.
90   If the dynamics are stiff, or a significant jump in the equations or the state vector has taken place at the event,
91   a conservative (small) step should be employed. If not, then a larger time step may be appropriate.
92 
93   In the latter case, instead of explicitly setting the post-event time step,
94   the user may also choose a special option of keeping the time steps used before the event, which is a sort of 'petsc-decide'
95   strategy. For this, use special value 0. It will signal the TS to directly step to the time point it planned to visit
96   prior to the event interval detection. E.g. if a step t0 -> t1 was planned originally, and an event 'te' occurred, t0 < te < t1,
97   then after the event the TS will step: te -> t1.
98   Moreover, in this situation the originally planned subsequent step t1 -> t2 will also be preserved.
99 
100   This function can be called not only in the initial setup, but also inside the `postevent()` callback set with `TSSetEventHandler()`,
101   affecting the post-event step for the current event, and the subsequent ones.
102   So, the strategy of the post-event time step definition can be adjusted on the fly.
103   Even if several events have been triggered in the given time point, only a single postevent handler is invoked,
104   and the user is to determine what post-event time step is more appropriate in this situation.
105 
106   By default (on `TSSetEventHandler()` call), the post-event time step is set equal to the (initial) `TS` time step.
107 
108 .seealso: [](ch_ts), `TS`, `TSEvent`, `TSSetEventHandler()`
109 @*/
110 PetscErrorCode TSSetPostEventStep(TS ts, PetscReal dt)
111 {
112   PetscFunctionBegin;
113   ts->event->timestep_postevent = dt;
114   PetscFunctionReturn(PETSC_SUCCESS);
115 }
116 
117 /*@
118   TSSetPostEventIntervalStep - Set the time-step used immediately following an event interval
119 
120   Logically Collective
121 
122   Input Parameters:
123 + ts - time integration context
124 - dt - post-event interval step
125 
126   Options Database Key:
127 . -ts_event_post_eventinterval_step <dt> - time-step after event interval
128 
129   Level: advanced
130 
131   Notes:
132   This function is deprecated, and its invocation will throw a runtime error. Use `TSSetPostEventStep()`.
133 
134 .seealso: [](ch_ts), `TS`, `TSEvent`, `TSSetEventHandler()`
135 @*/
136 PetscErrorCode TSSetPostEventIntervalStep(TS ts, PetscReal dt)
137 {
138   PetscFunctionBegin;
139   //ts->event->timestep_posteventinterval = dt;
140   /*
141      This deprecated function is set to always throw a runtime error. Attempting to reproduce its original behaviour,
142      i.e. setting the second (not the first) step after event, would break the logic of the TSEventHandler new code.
143   */
144   PetscCheck(PETSC_FALSE, PetscObjectComm((PetscObject)ts), PETSC_ERR_SUP, "TSSetPostEventIntervalStep() is deprecated --> TSSetPostEventStep() should be used instead");
145   PetscFunctionReturn(PETSC_SUCCESS);
146 }
147 
148 /*@
149   TSSetEventTolerances - Set tolerances for event (indicator function) zero crossings
150 
151   Logically Collective
152 
153   Input Parameters:
154 + ts   - time integration context
155 . tol  - tolerance, `PETSC_DECIDE` or `PETSC_DEFAULT` to leave the current value
156 - vtol - array of tolerances or `NULL`, used in preference to `tol` if present
157 
158   Options Database Key:
159 . -ts_event_tol <tol> - tolerance for event (indicator function) zero crossing
160 
161   Level: beginner
162 
163   Notes:
164   One must call `TSSetEventHandler()` before setting the tolerances.
165 
166   The size of `vtol` should be equal to the number of events on the given process.
167 
168   This function can be also called from the `postevent()` callback set with `TSSetEventHandler()`,
169   to adjust the tolerances on the fly.
170 
171 .seealso: [](ch_ts), `TS`, `TSEvent`, `TSSetEventHandler()`
172 @*/
173 PetscErrorCode TSSetEventTolerances(TS ts, PetscReal tol, PetscReal vtol[])
174 {
175   TSEvent event;
176 
177   PetscFunctionBegin;
178   PetscValidHeaderSpecific(ts, TS_CLASSID, 1);
179   if (vtol) PetscAssertPointer(vtol, 3);
180   PetscCheck(ts->event, PetscObjectComm((PetscObject)ts), PETSC_ERR_USER, "Must set the events first by calling TSSetEventHandler()");
181 
182   event = ts->event;
183   if (vtol) {
184     for (PetscInt i = 0; i < event->nevents; i++) event->vtol[i] = vtol[i];
185   } else {
186     if (tol != (PetscReal)PETSC_DECIDE && tol != (PetscReal)PETSC_DEFAULT) {
187       for (PetscInt i = 0; i < event->nevents; i++) event->vtol[i] = tol;
188     }
189   }
190   PetscFunctionReturn(PETSC_SUCCESS);
191 }
192 
193 /*@C
194   TSSetEventHandler - Sets functions and parameters used for indicating events and handling them
195 
196   Logically Collective
197 
198   Input Parameters:
199 + ts            - the `TS` context obtained from `TSCreate()`
200 . nevents       - number of local events (i.e. managed by the given MPI process)
201 . direction     - direction of zero crossing to be detected (one for each local event).
202                   `-1` => zero crossing in negative direction,
203                   `+1` => zero crossing in positive direction, `0` => both ways
204 . terminate     - flag to indicate whether time stepping should be terminated after
205                   an event is detected (one for each local event)
206 . indicator     - callback defininig the user indicator functions whose sign changes (see `direction`) mark presence of the events
207 . postevent     - [optional] user post-event callback; it can change the solution, ODE etc at the time of the event
208 - ctx           - [optional] user-defined context for private data for the
209                   `indicator()` and `postevent()` routines (use `NULL` if no
210                   context is desired)
211 
212   Calling sequence of `indicator`:
213 + ts     - the `TS` context
214 . t      - current time
215 . U      - current solution
216 . fvalue - output array with values of local indicator functions (length == `nevents`) for time t and state-vector U
217 - ctx    - the context passed as the final argument to `TSSetEventHandler()`
218 
219   Calling sequence of `postevent`:
220 + ts           - the `TS` context
221 . nevents_zero - number of triggered local events (whose indicator function is marked as crossing zero, and direction is appropriate)
222 . events_zero  - indices of the triggered local events
223 . t            - current time
224 . U            - current solution
225 . forwardsolve - flag to indicate whether `TS` is doing a forward solve (`PETSC_TRUE`) or adjoint solve (`PETSC_FALSE`)
226 - ctx          - the context passed as the final argument to `TSSetEventHandler()`
227 
228   Options Database Keys:
229 + -ts_event_tol <tol>                       - tolerance for zero crossing check of indicator functions
230 . -ts_event_monitor                         - print choices made by event handler
231 . -ts_event_recorder_initial_size <recsize> - initial size of event recorder
232 . -ts_event_post_event_step <dt>            - time step after event
233 - -ts_event_dt_min <dt>                     - minimum time step considered for TSEvent
234 
235   Level: intermediate
236 
237   Notes:
238   The indicator functions should be defined in the `indicator` callback using the components of solution `U` and/or time `t`.
239   Note that `U` is `PetscScalar`-valued, and the indicator functions are `PetscReal`-valued. It is the user's responsibility to
240   properly handle this difference, e.g. by applying `PetscRealPart()` or other appropriate conversion means.
241 
242   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.
243   However, the `postevent()` callback invocation is performed synchronously on all processes, including
244   those processes which have not currently triggered any events.
245 
246 .seealso: [](ch_ts), `TSEvent`, `TSCreate()`, `TSSetTimeStep()`, `TSSetConvergedReason()`
247 @*/
248 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)
249 {
250   TSAdapt   adapt;
251   PetscReal hmin;
252   TSEvent   event;
253   PetscBool flg;
254 #if defined PETSC_USE_REAL_SINGLE
255   PetscReal tol = 1e-4;
256 #else
257   PetscReal tol = 1e-6;
258 #endif
259 
260   PetscFunctionBegin;
261   PetscValidHeaderSpecific(ts, TS_CLASSID, 1);
262   if (nevents) {
263     PetscAssertPointer(direction, 3);
264     PetscAssertPointer(terminate, 4);
265   }
266   PetscCall(PetscNew(&event));
267   PetscCall(PetscMalloc1(nevents, &event->fvalue_prev));
268   PetscCall(PetscMalloc1(nevents, &event->fvalue));
269   PetscCall(PetscMalloc1(nevents, &event->fvalue_right));
270   PetscCall(PetscMalloc1(nevents, &event->fsign_prev));
271   PetscCall(PetscMalloc1(nevents, &event->fsign));
272   PetscCall(PetscMalloc1(nevents, &event->fsign_right));
273   PetscCall(PetscMalloc1(nevents, &event->side));
274   PetscCall(PetscMalloc1(nevents, &event->side_prev));
275   PetscCall(PetscMalloc1(nevents, &event->justrefined_AB));
276   PetscCall(PetscMalloc1(nevents, &event->gamma_AB));
277   PetscCall(PetscMalloc1(nevents, &event->direction));
278   PetscCall(PetscMalloc1(nevents, &event->terminate));
279   PetscCall(PetscMalloc1(nevents, &event->events_zero));
280   PetscCall(PetscMalloc1(nevents, &event->vtol));
281   for (PetscInt i = 0; i < nevents; i++) {
282     event->direction[i]      = direction[i];
283     event->terminate[i]      = terminate[i];
284     event->justrefined_AB[i] = PETSC_FALSE;
285     event->gamma_AB[i]       = 1;
286     event->side[i]           = 2;
287     event->side_prev[i]      = 0;
288   }
289   event->iterctr            = 0;
290   event->processing         = PETSC_FALSE;
291   event->revisit_right      = PETSC_FALSE;
292   event->nevents            = nevents;
293   event->indicator          = indicator;
294   event->postevent          = postevent;
295   event->ctx                = ctx;
296   event->timestep_postevent = ts->time_step;
297   PetscCall(TSGetAdapt(ts, &adapt));
298   PetscCall(TSAdaptGetStepLimits(adapt, &hmin, NULL));
299   event->timestep_min = hmin;
300 
301   event->recsize = 8; /* Initial size of the recorder */
302   PetscOptionsBegin(((PetscObject)ts)->comm, ((PetscObject)ts)->prefix, "TS Event options", "TS");
303   {
304     PetscCall(PetscOptionsReal("-ts_event_tol", "Tolerance for zero crossing check of indicator functions", "TSSetEventTolerances", tol, &tol, NULL));
305     PetscCall(PetscOptionsName("-ts_event_monitor", "Print choices made by event handler", "", &flg));
306     PetscCall(PetscOptionsInt("-ts_event_recorder_initial_size", "Initial size of event recorder", "", event->recsize, &event->recsize, NULL));
307     PetscCall(PetscOptionsReal("-ts_event_post_event_step", "Time step after event", "", event->timestep_postevent, &event->timestep_postevent, NULL));
308     PetscCall(PetscOptionsDeprecated("-ts_event_post_eventinterval_step", NULL, "3.20", "Use -ts_event_post_event_step"));
309     PetscCall(PetscOptionsReal("-ts_event_dt_min", "Minimum time step considered for TSEvent", "", event->timestep_min, &event->timestep_min, NULL));
310   }
311   PetscOptionsEnd();
312 
313   PetscCall(PetscMalloc1(event->recsize, &event->recorder.time));
314   PetscCall(PetscMalloc1(event->recsize, &event->recorder.stepnum));
315   PetscCall(PetscMalloc1(event->recsize, &event->recorder.nevents));
316   PetscCall(PetscMalloc1(event->recsize, &event->recorder.eventidx));
317   for (PetscInt i = 0; i < event->recsize; i++) PetscCall(PetscMalloc1(event->nevents, &event->recorder.eventidx[i]));
318   /* Initialize the event recorder */
319   event->recorder.ctr = 0;
320 
321   for (PetscInt i = 0; i < event->nevents; i++) event->vtol[i] = tol;
322   if (flg) PetscCall(PetscViewerASCIIOpen(PETSC_COMM_SELF, "stdout", &event->monitor));
323 
324   PetscCall(TSEventDestroy(&ts->event));
325   ts->event        = event;
326   ts->event->refct = 1;
327   PetscFunctionReturn(PETSC_SUCCESS);
328 }
329 
330 /*
331   TSEventRecorderResize - Resizes (2X) the event recorder arrays whenever the recording limit (event->recsize)
332                           is reached.
333 */
334 static PetscErrorCode TSEventRecorderResize(TSEvent event)
335 {
336   PetscReal *time;
337   PetscInt  *stepnum, *nevents;
338   PetscInt **eventidx;
339   PetscInt   fact = 2;
340 
341   PetscFunctionBegin;
342   /* Create larger arrays */
343   PetscCall(PetscMalloc1(fact * event->recsize, &time));
344   PetscCall(PetscMalloc1(fact * event->recsize, &stepnum));
345   PetscCall(PetscMalloc1(fact * event->recsize, &nevents));
346   PetscCall(PetscMalloc1(fact * event->recsize, &eventidx));
347   for (PetscInt i = 0; i < fact * event->recsize; i++) PetscCall(PetscMalloc1(event->nevents, &eventidx[i]));
348 
349   /* Copy over data */
350   PetscCall(PetscArraycpy(time, event->recorder.time, event->recsize));
351   PetscCall(PetscArraycpy(stepnum, event->recorder.stepnum, event->recsize));
352   PetscCall(PetscArraycpy(nevents, event->recorder.nevents, event->recsize));
353   for (PetscInt i = 0; i < event->recsize; i++) PetscCall(PetscArraycpy(eventidx[i], event->recorder.eventidx[i], event->recorder.nevents[i]));
354 
355   /* Destroy old arrays */
356   for (PetscInt i = 0; i < event->recsize; i++) PetscCall(PetscFree(event->recorder.eventidx[i]));
357   PetscCall(PetscFree(event->recorder.eventidx));
358   PetscCall(PetscFree(event->recorder.nevents));
359   PetscCall(PetscFree(event->recorder.stepnum));
360   PetscCall(PetscFree(event->recorder.time));
361 
362   /* Set pointers */
363   event->recorder.time     = time;
364   event->recorder.stepnum  = stepnum;
365   event->recorder.nevents  = nevents;
366   event->recorder.eventidx = eventidx;
367 
368   /* Update the size */
369   event->recsize *= fact;
370   PetscFunctionReturn(PETSC_SUCCESS);
371 }
372 
373 /*
374   Helper routine to handle user postevents and recording
375 */
376 static PetscErrorCode TSPostEvent(TS ts, PetscReal t, Vec U)
377 {
378   TSEvent   event        = ts->event;
379   PetscBool restart      = PETSC_FALSE;
380   PetscBool terminate    = PETSC_FALSE;
381   PetscBool statechanged = PETSC_FALSE;
382   PetscInt  ctr, stepnum;
383   PetscBool inflag[3], outflag[3];
384   PetscBool forwardsolve = PETSC_TRUE; // Flag indicating that TS is doing a forward solve
385 
386   PetscFunctionBegin;
387   if (event->postevent) {
388     PetscObjectState state_prev, state_post;
389     PetscCall(PetscObjectStateGet((PetscObject)U, &state_prev));
390     PetscCallBack("TSEvent post-event processing", (*event->postevent)(ts, event->nevents_zero, event->events_zero, t, U, forwardsolve, event->ctx)); // TODO update 'restart' here?
391     PetscCall(PetscObjectStateGet((PetscObject)U, &state_post));
392     if (state_prev != state_post) {
393       restart      = PETSC_TRUE;
394       statechanged = PETSC_TRUE;
395     }
396   }
397 
398   // Handle termination events and step restart
399   for (PetscInt i = 0; i < event->nevents_zero; i++)
400     if (event->terminate[event->events_zero[i]]) terminate = PETSC_TRUE;
401   inflag[0] = restart;
402   inflag[1] = terminate;
403   inflag[2] = statechanged;
404   PetscCall(MPIU_Allreduce(inflag, outflag, 3, MPIU_BOOL, MPI_LOR, ((PetscObject)ts)->comm));
405   restart      = outflag[0];
406   terminate    = outflag[1];
407   statechanged = outflag[2];
408   if (restart) PetscCall(TSRestartStep(ts));
409   if (terminate) PetscCall(TSSetConvergedReason(ts, TS_CONVERGED_EVENT));
410 
411   /*
412     Recalculate the indicator functions and signs if the state has been changed by the user postevent callback.
413     Note! If the state HAS NOT changed, the existing event->fsign (equal to zero) is kept, which:
414     - might have been defined using the previous (now-possibly-overridden) event->vtol,
415     - might have been set to zero on reaching a small time step rather than using the vtol criterion.
416     This will enforce keeping event->fsign = 0 where the zero-crossings were actually marked,
417     resulting in a more consistent behaviour of fsign's.
418   */
419   if (statechanged) {
420     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));
421     PetscCall(VecLockReadPush(U));
422     PetscCallBack("TSEvent indicator", (*event->indicator)(ts, t, U, event->fvalue, event->ctx));
423     PetscCall(VecLockReadPop(U));
424     TSEventCalcSigns(event->nevents, event->fvalue, event->vtol, event->fsign); // note, event->vtol might have been changed by the postevent()
425   }
426 
427   // Record the event in the event recorder
428   PetscCall(TSGetStepNumber(ts, &stepnum));
429   ctr = event->recorder.ctr;
430   if (ctr == event->recsize) PetscCall(TSEventRecorderResize(event));
431   event->recorder.time[ctr]    = t;
432   event->recorder.stepnum[ctr] = stepnum;
433   event->recorder.nevents[ctr] = event->nevents_zero;
434   for (PetscInt i = 0; i < event->nevents_zero; i++) event->recorder.eventidx[ctr][i] = event->events_zero[i];
435   event->recorder.ctr++;
436   PetscFunctionReturn(PETSC_SUCCESS);
437 }
438 
439 // PetscClangLinter pragma disable: -fdoc-sowing-chars
440 /*
441   (modified) Anderson-Bjorck variant of regula falsi method, refines [tleft, t] or [t, tright] based on 'side' (-1 or +1).
442   The scaling parameter is defined based on the 'justrefined' flag, the history of repeats of 'side', and the threshold.
443   To escape certain failure modes, the algorithm may drift towards the bisection rule.
444   The value pointed to by 'side_prev' gets updated.
445   This function returns the new time step.
446 
447   The underlying pure Anderson-Bjorck algorithm was taken as described in
448   J.M. Fernandez-Diaz, C.O. Menendez-Perez "A common framework for modified Regula Falsi methods and new methods of this kind", 2023.
449   The modifications subsequently introduced have little effect on the behaviour for simple cases requiring only a few iterations
450   (some minor convergence slowdown may take place though), but the effect becomes more pronounced for tough cases requiring many iterations.
451   For the latter situation the speed-up may be order(s) of magnitude compared to the classical Anderson-Bjorck.
452   The modifications (the threshold trick, and the drift towards bisection) were tested and tweaked
453   based on a number of test functions from the mentioned paper.
454 */
455 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)
456 {
457   PetscReal      new_dt, scal = 1.0, scalB = 1.0, threshold = 0.0, power;
458   PetscInt       reps     = 0;
459   const PetscInt REPS_CAP = 8; // an upper bound to be imposed on 'reps' (set to 8, somewhat arbitrary number, found after some tweaking)
460 
461   // Preparations
462   if (justrefined) {
463     if (*side_prev * side > 0) *side_prev += side;     // the side keeps repeating -> increment the side counter (-ve or +ve)
464     else *side_prev = side;                            // reset the counter
465     reps      = PetscMin(*side_prev * side, REPS_CAP); // the length of the recent side-repeat series, including the current 'side'
466     threshold = PetscPowReal(0.5, reps) * 0.1;         // ad-hoc strategy for threshold calculation (involved some tweaking)
467   } else *side_prev = side;                            // initial reset of the counter
468 
469   // Time step calculation
470   if (side == -1) {
471     if (justrefined && fright != 0.0 && fleft != 0.0) {
472       scal  = (fright - f) / fright;
473       scalB = -f / fleft;
474     }
475   } else { // must be side == +1
476     if (justrefined && fleft != 0.0 && fright != 0.0) {
477       scal  = (fleft - f) / fleft;
478       scalB = -f / fright;
479     }
480   }
481 
482   if (scal < threshold) scal = 0.5;
483   if (reps > 1) *gamma *= scal; // side did not switch since the last time, accumulate gamma
484   else *gamma = 1.0;            // side switched -> reset gamma
485   power = PetscMax(0.0, (reps - 2.0) / (REPS_CAP - 2.0));
486   scal  = PetscPowReal(scalB / *gamma, power) * (*gamma); // mix the Anderson-Bjorck scaling and Bisection scaling
487 
488   if (side == -1) new_dt = scal * fleft / (scal * fleft - f) * (t - tleft);
489   else new_dt = f / (f - scal * fright) * (tright - t);
490   /*
491     In tough cases (e.g. a polynomial of high order), there is a failure mode for the standard Anderson-Bjorck,
492     when the new proposed point jumps from one end-point of the bracket to the other, however the bracket
493     is contracting very slowly. A larger threshold for 'scal' prevents entering this mode.
494     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,
495     the 'scal' drifts towards the bisection approach (via scalB), ensuring stable convergence.
496   */
497   return new_dt;
498 }
499 
500 /*
501   Checks if the current point (t) is the zero-crossing location, based on the indicator function signs and direction[]:
502   - using the dt_min criterion,
503   - using the vtol criterion.
504   The situation (fsign_prev, fsign) = (0, 0) is treated as staying in the near-zero-zone of the previous zero-crossing,
505   and is not marked as a new zero-crossing.
506   This function may update event->side[].
507 */
508 static PetscErrorCode TSEventTestZero(TS ts, PetscReal t)
509 {
510   TSEvent event = ts->event;
511 
512   PetscFunctionBegin;
513   for (PetscInt i = 0; i < event->nevents; i++) {
514     const PetscBool bracket_is_left = (event->fsign_prev[i] * event->fsign[i] < 0 && event->fsign[i] * event->direction[i] >= 0) ? PETSC_TRUE : PETSC_FALSE;
515 
516     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
517     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
518   }
519   PetscFunctionReturn(PETSC_SUCCESS);
520 }
521 
522 /*
523   Checks if [fleft, f] or [f, fright] are 'brackets', i.e. intervals with the sign change, satisfying the 'direction'.
524   The right interval is only checked if iterctr > 0 (i.e. Anderson-Bjorck refinement has started).
525   The intervals like [0, x] and [x, 0] are not counted as brackets, i.e. intervals with the sign change.
526   The function returns the 'side' value: -1 (left, or both are brackets), +1 (only right one), +2 (neither).
527 */
528 static inline PetscInt TSEventTestBracket(PetscInt fsign_left, PetscInt fsign, PetscInt fsign_right, PetscInt direction, PetscInt iterctr)
529 {
530   PetscInt side = 2;
531   if (fsign_left * fsign < 0 && fsign * direction >= 0) side = -1;
532   if (side != -1 && iterctr > 0 && fsign * fsign_right < 0 && fsign_right * direction >= 0) side = 1;
533   return side;
534 }
535 
536 /*
537   Caps the time steps, accounting for time span points.
538   It uses 'event->timestep_cache' as a time step to calculate the tolerance for tspan points detection. This
539   is done since the event resolution may result in significant time step refinement, and we don't use these small steps for tolerances.
540   To enhance the consistency of tspan points detection, tolerance 'tspan->worktol' is reused later in the TSSolve iteration.
541   If a user-defined step is cut by this function, the input uncut step is saved to adapt->dt_span_cached.
542   Flag 'user_dt' indicates if the step was defined by user.
543 */
544 static inline PetscReal TSEvent_dt_cap(TS ts, PetscReal t, PetscReal dt, PetscBool user_dt)
545 {
546   PetscReal res = dt;
547   if (ts->exact_final_time == TS_EXACTFINALTIME_MATCHSTEP) {
548     PetscReal maxdt    = ts->max_time - t; // this may be overriden by tspan
549     PetscBool cut_made = PETSC_FALSE;
550     PetscReal eps      = 10 * PETSC_MACHINE_EPSILON;
551     if (ts->tspan) {
552       PetscInt   ctr = ts->tspan->spanctr;
553       PetscInt   Ns  = ts->tspan->num_span_times;
554       PetscReal *st  = ts->tspan->span_times;
555 
556       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
557       if (ctr < Ns && PetscIsCloseAtTol(t, st[ctr], ts->tspan->worktol, 0)) {                                              // just hit a time span point
558         if (ctr + 1 < Ns) maxdt = st[ctr + 1] - t;                                                                         // ok to use the next time span point
559         else maxdt = ts->max_time - t;                                                                                     // can't use the next time span point: they have finished
560       } else if (ctr < Ns) maxdt = st[ctr] - t;                                                                            // haven't hit a time span point, use the nearest one
561     }
562     maxdt = PetscMin(maxdt, ts->max_time - t);
563     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()");
564 
565     if (PetscIsCloseAtTol(dt, maxdt, eps, 0)) res = maxdt; // no cut
566     else {
567       if (dt > maxdt) {
568         res      = maxdt; // yes cut
569         cut_made = PETSC_TRUE;
570       } else res = dt; // no cut
571     }
572     if (ts->adapt && user_dt) { // only update dt_span_cached for the user-defined step
573       if (cut_made) ts->adapt->dt_span_cached = dt;
574       else ts->adapt->dt_span_cached = 0;
575     }
576   }
577   return res;
578 }
579 
580 /*
581   Updates the left-end values
582 */
583 static inline void TSEvent_update_left(TSEvent event, PetscReal t)
584 {
585   for (PetscInt i = 0; i < event->nevents; i++) {
586     event->fvalue_prev[i] = event->fvalue[i];
587     event->fsign_prev[i]  = event->fsign[i];
588   }
589   event->ptime_prev = t;
590 }
591 
592 /*
593   Updates the right-end values
594 */
595 static inline void TSEvent_update_right(TSEvent event, PetscReal t)
596 {
597   for (PetscInt i = 0; i < event->nevents; i++) {
598     event->fvalue_right[i] = event->fvalue[i];
599     event->fsign_right[i]  = event->fsign[i];
600   }
601   event->ptime_right = t;
602 }
603 
604 /*
605   Updates the current values from the right-end values
606 */
607 static inline PetscReal TSEvent_update_from_right(TSEvent event)
608 {
609   for (PetscInt i = 0; i < event->nevents; i++) {
610     event->fvalue[i] = event->fvalue_right[i];
611     event->fsign[i]  = event->fsign_right[i];
612   }
613   return event->ptime_right;
614 }
615 
616 // PetscClangLinter pragma disable: -fdoc-section-spacing
617 // PetscClangLinter pragma disable: -fdoc-section-header-unknown
618 // PetscClangLinter pragma disable: -fdoc-section-header-spelling
619 // PetscClangLinter pragma disable: -fdoc-section-header-missing
620 // PetscClangLinter pragma disable: -fdoc-section-header-fishy-header
621 // PetscClangLinter pragma disable: -fdoc-param-list-func-parameter-documentation
622 // PetscClangLinter pragma disable: -fdoc-synopsis-missing-description
623 // PetscClangLinter pragma disable: -fdoc-sowing-chars
624 /*
625   TSEventHandler - the main function to perform a single iteration of event detection.
626 
627   Developer notes:
628   a) The 'event->iterctr > 0' is used as an indicator that Anderson-Bjorck refinement has started.
629   b) If event->iterctr == 0, then justrefined_AB[i] is always false.
630   c) The right-end quantities: ptime_right, fvalue_right[i] and fsign_right[i] are only guaranteed to be valid
631   for event->iterctr > 0.
632   d) If event->iterctr > 0, then event->processing is PETSC_TRUE; the opposite may not hold.
633   e) event->side[i] may take values: 0 <=> point t is a zero-crossing for indicator function i (via vtol/dt_min criterion);
634   -1/+1 <=> detected a bracket to the left/right of t for indicator function i; +2 <=> no brackets/zero-crossings.
635   f) 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
636   smaller than the tolerance. Besides, zero sign is enforced after marking a zero-crossing due to small bracket size criterion.
637 
638   The intervals with the indicator function sign change (i.e. containing the potential zero-crossings) are called 'brackets'.
639   To find a zero-crossing, the algorithm first locates a bracket, and then sequentially subdivides it, generating a sequence
640   of brackets whose length tends to zero. The bracket subdivision involves the (modified) Anderson-Bjorck method.
641 
642   Apart from the comments scattered throughout the code to clarify different lines and blocks,
643   a few tricky aspects of the algorithm (and the underlying reasoning) are discussed in detail below:
644 
645   =Sign tracking=
646   When a zero-crossing is found, the sign variable (event->fsign[i]) is set to zero for the current point t.
647   This happens both for zero-crossings triggered via the vtol criterion, and those triggered via the dt_min
648   criterion. After the event, as the TS steps forward, the current sign values are handed over to event->fsign_prev[i].
649   The recalculation of signs is avoided if possible: e.g. if a 'vtol' criterion resulted in a zero-crossing at point t,
650   but the subsequent call to postevent() handler decreased 'vtol', making the indicator function no longer "close to zero"
651   at point t, the fsign[i] will still consistently keep the zero value. This allows avoiding the erroneous duplication
652   of events:
653 
654   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.
655   Suppose the postevent() handler changes vtol to vtol*, such that abs(f1) > vtol*. The TS makes a step t1 -> t3, where
656   again f1 < 0, f3 > 0, and the event handler will find a new event near t1, which is actually a duplication of the
657   original event at t1. The duplications are avoided by NOT counting the sign progressions 0 -> +1, or 0 -> -1
658   as brackets. Tracking (instead of recalculating) the sign values makes this procedure work more consistently.
659 
660   The sign values are however recalculated if the postevent() callback has changed the current solution vector U
661   (such a change resets everything).
662   The sign value is also set to zero if the dt_min criterion has triggered the event. This allows the algorithm to
663   work consistently, irrespective of the type of criterion involved (vtol/dt_min).
664 
665   =Event from min bracket=
666   When the event handler ends up with a bracket [t0, t1] with size <= dt_min, a zero crossing is reported at t1,
667   and never at t0. If such a bracket is discovered when TS is staying at t0, one more step forward (to t1) is necessary
668   to mark the found event. This is the situation of revisiting t1, which is described below (see Revisiting).
669 
670   Why t0 is not reported as event location? Suppose it is, and let f0 < 0, f1 > 0. Also suppose that the
671   postevent() handler has slightly changed the solution U, so the sign at t0 is recalculated: it equals -1. As the TS steps
672   further: t0 -> t2, with sign0 == -1, and sign2 == +1, the event handler will locate the bracket [t0, t2], eventually
673   resolving a new event near t1, i.e. finding a duplicate event.
674   This situation is avoided by reporting the event at t1 in the first place.
675 
676   =Revisiting=
677   When handling the situation with small bracket size, the TS solver may happen to visit the same point twice,
678   but with different results.
679 
680   E.g. originally it discovered a bracket with sign change [t0, t10], and started resolving the zero-crossing,
681   visiting the points t1,...,t9 : t0 < t1 < ... < t9 < t10. Suppose that at t9 the algorithm discovers
682   that [t9, t10] is a bracket with the sign change it was looking for, and that |t10 - t9| is too small.
683   So point t10 should be revisited and marked as the zero crossing (by the minimum bracket size criterion).
684   On re-visiting t10, via the refined sequence of steps t0,...,t10, the TS solver may arrive at a solution U*
685   different from the solution U it found at t10 originally. Hence, the indicator functions at t10 may become different,
686   and the condition of the sign change, which existed originally, may disappear, breaking the logic of the algorithm.
687 
688   To handle such (-=unlikely=-, but possible) situations, two strategies can be considered:
689   1) [not used here] Allow the brackets with sign change to disappear during iterations. The algorithm should be able
690   to cleanly exit the iteration and leave all the objects/variables/caches involved in a valid state.
691   2) [ADOPTED HERE!] On revisiting t10, the event handler reuses the indicator functions previously calculated for the
692   original solution U. This U may be less precise than U*, but this trick does not allow the algorithm logic to break down.
693   HOWEVER, the original U is not stored anywhere, it is essentially lost since the TS performed the rollback from it.
694   On revisiting t10, the updated solution U* will inevitably be found and used everywhere EXCEPT the current
695   indicator functions calculation, e.g. U* will be used in the postevent() handler call. Since t10 is the event location,
696   the appropriate indicator-function-signs will be enforced to be 0 (regardless if the solution was U or U*).
697   If the solution is then changed by the postevent(), the indicator-function-signs will be recalculated.
698 
699   Whether the algorithm is revisiting a point in the current TSEventHandler() call is flagged by 'event->revisit_right'.
700 */
701 PetscErrorCode TSEventHandler(TS ts)
702 {
703   TSEvent   event;
704   PetscReal t, dt_next = 0.0;
705   Vec       U;
706   PetscInt  minsidein = 2, minsideout = 2; // minsideout is sync on all ranks
707   PetscBool finished = PETSC_FALSE;        // should stay sync on all ranks
708   PetscBool revisit_right_cache;           // [sync] flag for inner consistency checks
709 
710   PetscFunctionBegin;
711   PetscValidHeaderSpecific(ts, TS_CLASSID, 1);
712 
713   if (!ts->event) PetscFunctionReturn(PETSC_SUCCESS);
714   event               = ts->event;
715   event->nevents_zero = 0;
716   revisit_right_cache = event->revisit_right;
717   for (PetscInt i = 0; i < event->nevents; i++) event->side[i] = 2; // side's are reset on each new iteration
718   if (event->iterctr == 0)
719     for (PetscInt i = 0; i < event->nevents; i++) event->justrefined_AB[i] = PETSC_FALSE;
720 
721   PetscCall(TSGetTime(ts, &t));
722   if (!event->processing) { // update the caches
723     PetscReal dt;
724     PetscCall(TSGetTimeStep(ts, &dt));
725     event->ptime_cache    = t;
726     event->timestep_cache = dt; // the next TS move is planned to be: t -> t+dt
727   }
728 
729   PetscCall(TSGetSolution(ts, &U)); // if revisiting, this will be the updated U* (see discussion on "Revisiting" in the Developer notes above)
730   if (event->revisit_right) {
731     PetscReal tr = TSEvent_update_from_right(event);
732     PetscCheck(PetscAbsReal(tr - t) < PETSC_SMALL, PetscObjectComm((PetscObject)ts), PETSC_ERR_PLIB, "Inconsistent time value when performing 'revisiting' in TSEventHandler()");
733   } else {
734     PetscCall(VecLockReadPush(U));
735     PetscCallBack("TSEvent indicator", (*event->indicator)(ts, t, U, event->fvalue, event->ctx)); // fill fvalue's at point 't'
736     PetscCall(VecLockReadPop(U));
737     TSEventCalcSigns(event->nevents, event->fvalue, event->vtol, event->fsign); // fill fvalue signs
738   }
739   PetscCall(TSEventTestZero(ts, t)); // check if the current point 't' is the event location; event->side[] may get updated
740 
741   for (PetscInt i = 0; i < event->nevents; i++) { // check for brackets on the left/right of 't'
742     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);
743     minsidein = PetscMin(minsidein, event->side[i]);
744   }
745   PetscCall(MPIU_Allreduce(&minsidein, &minsideout, 1, MPIU_INT, MPI_MIN, PetscObjectComm((PetscObject)ts)));
746   /*
747     minsideout (sync on all ranks) indicates the minimum of the following states:
748     -1 : [ptime_prev, t] is a bracket for some indicator-function-i
749     +1 : [t, ptime_right] is a bracket for some indicator-function-i
750      0 : t is a zero-crossing for some indicator-function-i
751      2 : none of the above
752   */
753   PetscCheck(!event->revisit_right || minsideout == 0, PetscObjectComm((PetscObject)ts), PETSC_ERR_PLIB, "minsideout != 0 when performing 'revisiting' in TSEventHandler()");
754 
755   if (minsideout == -1 || minsideout == +1) {                                                           // this if-branch will refine the left/right bracket
756     const PetscReal bracket_size = (minsideout == -1) ? t - event->ptime_prev : event->ptime_right - t; // sync on all ranks
757 
758     if (minsideout == +1 && bracket_size <= event->timestep_min) { // check if the bracket (right) is small
759       // [--------------------|-]
760       dt_next              = bracket_size; // need one more step to get to event->ptime_right
761       event->revisit_right = PETSC_TRUE;
762       TSEvent_update_left(event, t);
763       if (event->monitor)
764         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,
765                                          (double)event->ptime_right, (double)(event->ptime_prev + dt_next)));
766     } else { // the bracket is not very small -> refine it
767       // [--------|-------------]
768       if (bracket_size <= 2 * event->timestep_min) dt_next = bracket_size / 2; // the bracket is almost small -> bisect it
769       else {                                                                   // the bracket is not small -> use Anderson-Bjorck
770         PetscReal dti_min = PETSC_MAX_REAL;
771         for (PetscInt i = 0; i < event->nevents; i++) {
772           if (event->side[i] == minsideout) { // only refine the appropriate brackets
773             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]);
774             dti_min       = PetscMin(dti_min, dti);
775           }
776         }
777         PetscCall(MPIU_Allreduce(&dti_min, &dt_next, 1, MPIU_REAL, MPIU_MIN, PetscObjectComm((PetscObject)ts)));
778         if (dt_next < event->timestep_min) dt_next = event->timestep_min;
779         if (bracket_size - dt_next < event->timestep_min) dt_next = bracket_size - event->timestep_min;
780       }
781 
782       if (minsideout == -1) { // minsideout == -1, update the right-end values, retain the left-end values
783         TSEvent_update_right(event, t);
784         PetscCall(TSRollBack(ts));
785         PetscCall(TSSetConvergedReason(ts, TS_CONVERGED_ITERATING)); // e.g. to override TS_CONVERGED_TIME on reaching ts->max_time
786       } else TSEvent_update_left(event, t);                          // minsideout == +1, update the left-end values, retain the right-end values
787 
788       for (PetscInt i = 0; i < event->nevents; i++) { // update the "Anderson-Bjorck" flags
789         if (event->side[i] == minsideout) {
790           event->justrefined_AB[i] = PETSC_TRUE; // only for these i's Anderson-Bjorck was invoked
791           if (event->monitor)
792             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,
793                                              (double)event->ptime_right, (double)(event->ptime_prev + dt_next)));
794         } else event->justrefined_AB[i] = PETSC_FALSE; // for these i's Anderson-Bjorck was not invoked
795       }
796     }
797     event->iterctr++;
798     event->processing = PETSC_TRUE;
799   } else if (minsideout == 0) { // found the appropriate zero-crossing (and no brackets to the left), finishing!
800     // [--------0-------------]
801     finished             = PETSC_TRUE;
802     event->revisit_right = PETSC_FALSE;
803     for (PetscInt i = 0; i < event->nevents; i++)
804       if (event->side[i] == minsideout) {
805         event->events_zero[event->nevents_zero++] = i;
806         if (event->fsign[i] == 0) { // vtol was engaged
807           if (event->monitor)
808             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]));
809         } else {               // dt_min was engaged
810           event->fsign[i] = 0; // sign = 0 is enforced further
811           if (event->monitor)
812             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,
813                                              (double)event->ptime_prev, (double)t));
814         }
815       }
816     event->iterctr++;
817     event->processing = PETSC_TRUE;
818   } else { // minsideout == 2: no brackets, no zero-crossings
819     // [----------------------]
820     PetscCheck(event->iterctr == 0, PetscObjectComm((PetscObject)ts), PETSC_ERR_PLIB, "Unexpected state (event->iterctr != 0) in TSEventHandler()");
821     if (event->processing) PetscCall(TSSetTimeStep(ts, TSEvent_dt_cap(ts, t, event->timestep_cache, PETSC_FALSE)));
822     event->processing = PETSC_FALSE;
823   }
824 
825   // if 'revisit_right' was flagged before the current iteration started, the iteration is expected to finish
826   PetscCheck(!revisit_right_cache || finished, PetscObjectComm((PetscObject)ts), PETSC_ERR_PLIB, "Unexpected state of 'revisit_right_cache' in TSEventHandler()");
827 
828   if (finished) { // finished handling the current event
829     PetscCall(TSPostEvent(ts, t, U));
830 
831     PetscReal dt;
832     PetscBool user_dt = PETSC_FALSE;
833     if (event->timestep_postevent > 0) {
834       dt                = event->timestep_postevent; // user-provided post-event dt
835       event->processing = PETSC_FALSE;
836       user_dt           = PETSC_TRUE;
837     } else {
838       dt                = event->ptime_cache - t; // 'petsc-decide' the post-event dt
839       event->processing = PETSC_TRUE;
840       if (PetscAbsReal(dt) < PETSC_SMALL) {
841         dt                = event->timestep_cache; // we hit the event, continue with the cached time step
842         event->processing = PETSC_FALSE;
843       }
844     }
845     PetscCall(TSSetTimeStep(ts, TSEvent_dt_cap(ts, t, dt, user_dt)));
846     event->iterctr = 0;
847   } // if-finished
848 
849   if (event->iterctr == 0) TSEvent_update_left(event, t); // not found an event, or finished the event
850   else {
851     PetscCall(TSGetTime(ts, &t));                                              // update 't' to account for potential rollback
852     PetscCall(TSSetTimeStep(ts, TSEvent_dt_cap(ts, t, dt_next, PETSC_FALSE))); // continue resolving the event
853   }
854   PetscFunctionReturn(PETSC_SUCCESS);
855 }
856 
857 PetscErrorCode TSAdjointEventHandler(TS ts)
858 {
859   TSEvent   event;
860   PetscReal t;
861   Vec       U;
862   PetscInt  ctr;
863   PetscBool forwardsolve = PETSC_FALSE; // Flag indicating that TS is doing an adjoint solve
864 
865   PetscFunctionBegin;
866   PetscValidHeaderSpecific(ts, TS_CLASSID, 1);
867   if (!ts->event) PetscFunctionReturn(PETSC_SUCCESS);
868   event = ts->event;
869 
870   PetscCall(TSGetTime(ts, &t));
871   PetscCall(TSGetSolution(ts, &U));
872 
873   ctr = event->recorder.ctr - 1;
874   if (ctr >= 0 && PetscAbsReal(t - event->recorder.time[ctr]) < PETSC_SMALL) {
875     // Call the user post-event function
876     if (event->postevent) {
877       PetscCallBack("TSEvent post-event processing", (*event->postevent)(ts, event->recorder.nevents[ctr], event->recorder.eventidx[ctr], t, U, forwardsolve, event->ctx));
878       event->recorder.ctr--;
879     }
880   }
881   PetscCall(PetscBarrier((PetscObject)ts));
882   PetscFunctionReturn(PETSC_SUCCESS);
883 }
884 
885 /*@
886   TSGetNumEvents - Get the number of events defined on the given MPI process
887 
888   Logically Collective
889 
890   Input Parameter:
891 . ts - the `TS` context
892 
893   Output Parameter:
894 . nevents - the number of local events on each MPI process
895 
896   Level: intermediate
897 
898 .seealso: [](ch_ts), `TSEvent`, `TSSetEventHandler()`
899 @*/
900 PetscErrorCode TSGetNumEvents(TS ts, PetscInt *nevents)
901 {
902   PetscFunctionBegin;
903   *nevents = ts->event->nevents;
904   PetscFunctionReturn(PETSC_SUCCESS);
905 }
906